nautobot 2.1.7__py3-none-any.whl → 2.1.9__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/api.py +1 -2
- nautobot/apps/utils.py +4 -0
- nautobot/apps/views.py +2 -0
- nautobot/circuits/api/urls.py +1 -2
- nautobot/circuits/api/views.py +0 -12
- nautobot/circuits/tests/integration/test_relationships.py +0 -4
- nautobot/core/api/routers.py +25 -3
- nautobot/core/api/utils.py +4 -0
- nautobot/core/api/views.py +21 -15
- nautobot/core/celery/schedulers.py +13 -0
- nautobot/core/choices.py +0 -21
- nautobot/core/models/__init__.py +1 -1
- nautobot/core/models/tree_queries.py +29 -7
- nautobot/core/releases.py +1 -1
- nautobot/core/settings.py +9 -0
- nautobot/core/settings_funcs.py +0 -18
- nautobot/core/signals.py +5 -5
- nautobot/core/tasks.py +7 -3
- nautobot/core/templates/admin/base.html +23 -94
- nautobot/core/templates/generic/object_list.html +2 -0
- nautobot/core/templates/graphene/graphiql.html +18 -47
- nautobot/core/templates/inc/footer.html +5 -5
- nautobot/core/templates/inc/nav_menu.html +0 -7
- nautobot/core/templates/nautobot_config.py.j2 +6 -0
- nautobot/core/templates/rest_framework/api.html +12 -5
- nautobot/core/testing/mixins.py +13 -5
- nautobot/core/tests/integration/test_plugin_navbar.py +7 -21
- nautobot/core/tests/integration/test_view_authentication.py +67 -0
- nautobot/core/tests/runner.py +25 -2
- nautobot/core/tests/test_graphql.py +2 -14
- nautobot/core/tests/test_models.py +3 -3
- nautobot/core/tests/test_navigations.py +67 -10
- nautobot/core/tests/test_releases.py +9 -3
- nautobot/core/tests/test_views.py +23 -16
- nautobot/core/utils/lookup.py +124 -0
- nautobot/core/views/__init__.py +3 -7
- nautobot/core/views/generic.py +9 -0
- nautobot/dcim/api/urls.py +1 -2
- nautobot/dcim/api/views.py +1 -12
- nautobot/dcim/choices.py +56 -0
- nautobot/dcim/models/racks.py +1 -3
- nautobot/dcim/navigation.py +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +67 -43
- nautobot/dcim/tests/test_api.py +3 -0
- nautobot/dcim/tests/test_filters.py +0 -28
- nautobot/dcim/views.py +5 -2
- nautobot/extras/api/urls.py +1 -2
- nautobot/extras/api/views.py +0 -10
- nautobot/extras/choices.py +14 -0
- nautobot/extras/models/customfields.py +93 -34
- nautobot/extras/models/groups.py +1 -1
- nautobot/extras/models/relationships.py +32 -19
- nautobot/extras/navigation.py +3 -2
- nautobot/extras/plugins/__init__.py +8 -0
- nautobot/extras/plugins/views.py +6 -9
- nautobot/extras/querysets.py +1 -1
- nautobot/extras/signals.py +12 -6
- nautobot/extras/templates/extras/customfield.html +22 -14
- nautobot/extras/templatetags/job_buttons.py +7 -0
- nautobot/extras/templatetags/plugins.py +5 -1
- nautobot/extras/tests/test_customfields.py +323 -287
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_jobs.py +2 -2
- nautobot/extras/tests/test_plugins.py +41 -0
- nautobot/extras/tests/test_relationships.py +31 -14
- nautobot/extras/tests/test_views.py +124 -1
- nautobot/extras/utils.py +7 -3
- nautobot/extras/views.py +10 -10
- nautobot/ipam/api/urls.py +1 -2
- nautobot/ipam/api/views.py +6 -13
- nautobot/ipam/tables.py +0 -1
- nautobot/ipam/tests/test_graphql.py +2 -3
- nautobot/ipam/views.py +12 -10
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +30 -2
- nautobot/project-static/docs/apps/index.html +30 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +410 -410
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +386 -358
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +45 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +759 -602
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +528 -467
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +205 -109
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +1265 -785
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1827 -1746
- nautobot/project-static/docs/development/apps/api/configuration-view.html +30 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +31 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +30 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +30 -2
- nautobot/project-static/docs/development/apps/api/setup.html +30 -2
- nautobot/project-static/docs/development/apps/api/testing.html +33 -5
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +33 -5
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-detail-views.html +13 -5559
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +5594 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/tabs.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +44 -11
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +47 -14
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +30 -2
- nautobot/project-static/docs/development/apps/index.html +30 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +30 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +30 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +30 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +30 -2
- nautobot/project-static/docs/development/core/application-registry.html +30 -2
- nautobot/project-static/docs/development/core/best-practices.html +33 -5
- nautobot/project-static/docs/development/core/bootstrap-ui.html +30 -2
- nautobot/project-static/docs/development/core/caching.html +5481 -0
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +30 -2
- nautobot/project-static/docs/development/core/extending-models.html +33 -5
- nautobot/project-static/docs/development/core/generic-views.html +30 -2
- nautobot/project-static/docs/development/core/getting-started.html +49 -12
- nautobot/project-static/docs/development/core/homepage.html +30 -2
- nautobot/project-static/docs/development/core/index.html +30 -2
- nautobot/project-static/docs/development/core/model-features.html +30 -2
- nautobot/project-static/docs/development/core/natural-keys.html +30 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +30 -2
- nautobot/project-static/docs/development/core/release-checklist.html +30 -2
- nautobot/project-static/docs/development/core/role-internals.html +30 -2
- nautobot/project-static/docs/development/core/style-guide.html +30 -2
- nautobot/project-static/docs/development/core/templates.html +30 -2
- nautobot/project-static/docs/development/core/testing.html +30 -2
- nautobot/project-static/docs/development/core/user-preferences.html +30 -2
- nautobot/project-static/docs/development/index.html +30 -2
- nautobot/project-static/docs/development/jobs/index.html +30 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +30 -2
- nautobot/project-static/docs/index.html +30 -2
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/index.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +31 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +573 -134
- nautobot/project-static/docs/release-notes/version-2.0.html +30 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +539 -170
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +250 -240
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +49 -2
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +37 -5
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +6019 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +33 -5
- nautobot/project-static/docs/user-guide/administration/installation/services.html +30 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +30 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +30 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +30 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +33 -5
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +30 -2
- nautobot/project-static/docs/user-guide/index.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +111 -15
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +30 -2
- nautobot/tenancy/api/urls.py +1 -2
- nautobot/tenancy/api/views.py +0 -12
- nautobot/tenancy/navigation.py +1 -1
- nautobot/tenancy/tests/test_filters.py +0 -168
- nautobot/users/api/urls.py +1 -2
- nautobot/users/api/views.py +2 -65
- nautobot/users/views.py +8 -8
- nautobot/virtualization/api/urls.py +1 -2
- nautobot/virtualization/api/views.py +0 -12
- nautobot/virtualization/tests/test_filters.py +0 -28
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/METADATA +2 -2
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/RECORD +338 -334
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/NOTICE +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/WHEEL +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{% extends 'base.html' %}
|
|
2
2
|
{% load buttons %}
|
|
3
3
|
{% load helpers %}
|
|
4
|
+
{% load plugins %}
|
|
4
5
|
{% load static %}
|
|
5
6
|
|
|
6
7
|
{% block header %}
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
{% block content %}
|
|
37
38
|
<div class="pull-right noprint">
|
|
38
39
|
{% block buttons %}{% endblock %}
|
|
40
|
+
{% plugin_buttons content_type.model_class 'list' %}
|
|
39
41
|
{% if table and request.user.is_authenticated and table_config_form %}
|
|
40
42
|
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#ObjectTable_config" title="Configure table"><i class="mdi mdi-cog"></i> Configure</button>
|
|
41
43
|
{% endif %}
|
|
@@ -10,53 +10,31 @@ add "&raw" to the end of the URL within a browser.
|
|
|
10
10
|
<!DOCTYPE html>
|
|
11
11
|
<html>
|
|
12
12
|
<head>
|
|
13
|
+
{% include 'inc/media.html' %}
|
|
14
|
+
{% block extra_styles %}{% endblock %}
|
|
13
15
|
<!-- Nautobot template requirements -->
|
|
14
16
|
<title>{% block title %}GraphiQL{% endblock %} - {{ settings.BRANDING_TITLE }}</title>
|
|
15
|
-
|
|
16
|
-
href="{% static 'bootstrap-3.4.1-dist/css/bootstrap.min.css' %}"
|
|
17
|
-
onerror="window.location='{% url 'media_failure' %}?filename=bootstrap-3.4.1-dist/css/bootstrap.min.css'">
|
|
18
|
-
<link rel="stylesheet"
|
|
19
|
-
href="{% static 'materialdesignicons-6.5.95/css/materialdesignicons.min.css' %}"
|
|
20
|
-
onerror="window.location='{% url 'media_failure' %}?filename=materialdesignicons-6.5.95/css/materialdesignicons.min.css'">
|
|
21
|
-
<link rel="stylesheet"
|
|
22
|
-
href="{% static 'jquery-ui-1.13.1/jquery-ui.min.css' %}"
|
|
23
|
-
onerror="window.location='{% url 'media_failure' %}?filename=jquery-ui-1.13.1/jquery-ui.min.css'">
|
|
24
|
-
<link rel="stylesheet"
|
|
25
|
-
href="{% static 'select2-4.0.13/select2.min.css' %}"
|
|
26
|
-
onerror="window.location='{% url 'media_failure' %}?filename=select2-4.0.13/select2.min.css'">
|
|
27
|
-
<link rel="stylesheet"
|
|
28
|
-
href="{% static 'select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css' %}"
|
|
29
|
-
onerror="window.location='{% url 'media_failure' %}?filename=select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css'">
|
|
30
|
-
<link rel="stylesheet"
|
|
31
|
-
href="{% static 'flatpickr-4.6.9/themes/light.min.css' %}"
|
|
32
|
-
onerror="window.location='{% url 'media_failure' %}?filename=flatpickr-4.6.9/themes/light.min.css'">
|
|
33
|
-
<link rel="stylesheet" id="dark-theme"
|
|
34
|
-
href="{% versioned_static 'css/dark.css' %}"
|
|
35
|
-
onerror="window.location='{% url 'media_failure' %}?filename=css/dark.css'" disabled="disabled" />
|
|
36
|
-
<link rel="stylesheet"
|
|
37
|
-
href="{% versioned_static 'css/base.css' %}"
|
|
38
|
-
onerror="window.location='{% url 'media_failure' %}?filename=css/base.css'">
|
|
39
|
-
<link rel="apple-touch-icon" sizes="180x180" href="{% custom_branding_or_static 'icon_180' 'img/nautobot_icon_180x180.png' %}">
|
|
40
|
-
<link rel="icon" type="image/png" sizes="32x32" href="{% custom_branding_or_static 'icon_32' 'img/nautobot_icon_32x32.png' %}">
|
|
41
|
-
<link rel="icon" type="image/png" sizes="16x16" href="{% custom_branding_or_static 'icon_16' 'img/nautobot_icon_16x16.png' %}">
|
|
42
|
-
<link rel="icon" type="image/png" sizes="192x192" href="{% custom_branding_or_static 'icon_192' 'img/nautobot_icon_192x192.png' %}">
|
|
43
|
-
<link rel="mask-icon" type="image/png" color="#0097ff" href="{% custom_branding_or_static 'icon_mask' 'img/nautobot_icon_monochrome.png' %}">
|
|
44
|
-
<link rel="shortcut icon" href="{% custom_branding_or_static 'favicon' 'img/favicon.ico' %}">
|
|
45
|
-
<meta name="msapplication-TileColor" content="#2d89ef">
|
|
46
|
-
<meta name="theme-color" content="#ffffff">
|
|
47
|
-
<meta charset="UTF-8">
|
|
48
|
-
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
|
|
17
|
+
|
|
49
18
|
<!-- GraphiQL template requirements -->
|
|
50
19
|
<style>
|
|
51
|
-
|
|
52
|
-
height: 100%;
|
|
20
|
+
#editor {
|
|
53
21
|
margin: 0;
|
|
54
22
|
width: 100%;
|
|
23
|
+
height: calc(100vh - 56px - 3rem);
|
|
55
24
|
}
|
|
56
25
|
.toolbar .dropdown-menu > li > a {
|
|
57
26
|
clear: left;
|
|
58
27
|
margin-right: 120px; /* leave room for the "Save Changes" button if present */
|
|
59
28
|
}
|
|
29
|
+
.graphiql-container .doc-explorer-title-bar {
|
|
30
|
+
box-sizing: content-box;
|
|
31
|
+
}
|
|
32
|
+
.graphiql-container .history-title-bar {
|
|
33
|
+
box-sizing: content-box;
|
|
34
|
+
}
|
|
35
|
+
.graphiql-container .docExplorerShow {
|
|
36
|
+
white-space: nowrap;
|
|
37
|
+
}
|
|
60
38
|
</style>
|
|
61
39
|
<!-- As Nautobot may be run without internet access, we source these files locally rather than from an online CDN -->
|
|
62
40
|
<link rel="stylesheet"
|
|
@@ -72,23 +50,16 @@ add "&raw" to the end of the URL within a browser.
|
|
|
72
50
|
onerror="window.location='{% url 'media_failure' %}?filename=graphiql-1.5.16/graphiql.min.js'"></script>
|
|
73
51
|
<script src="{% static 'subscriptions-transport-ws-0.9.18/client.min.js' %}"
|
|
74
52
|
onerror="window.location='{% url 'media_failure' %}?filename=subscriptions-transport-ws-0.9.18/client.min.js'"></script>
|
|
75
|
-
<!-- Custom CSS to address some conflicts between the two -->
|
|
76
|
-
<style>
|
|
77
|
-
body {
|
|
78
|
-
padding-top: 54px;
|
|
79
|
-
}
|
|
80
|
-
.graphiql-container .doc-explorer-title-bar {
|
|
81
|
-
box-sizing: content-box;
|
|
82
|
-
}
|
|
83
|
-
</style>
|
|
84
53
|
</head>
|
|
85
54
|
<body>
|
|
86
55
|
<!-- Nautobot page contents -->
|
|
87
56
|
{% include 'inc/nav_menu.html' %}
|
|
88
57
|
{% include 'modals/modal_theme.html' with name='theme'%}
|
|
89
58
|
{% include 'inc/javascript.html' %}
|
|
90
|
-
|
|
91
|
-
|
|
59
|
+
<div class="container-fluid wrapper" id="main-content">
|
|
60
|
+
<!-- GraphiQL page contents -->
|
|
61
|
+
<div id="editor"></div>
|
|
62
|
+
</div>
|
|
92
63
|
{% include 'inc/footer.html' %}
|
|
93
64
|
{% csrf_token %}
|
|
94
65
|
<script type="application/javascript">
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
{% load plugins %}
|
|
4
4
|
|
|
5
5
|
<style>
|
|
6
|
-
/* Styles for viewports narrower than
|
|
7
|
-
@media (max-width:
|
|
6
|
+
/* Styles for viewports narrower than 1232px, i.e. bootstrap 'xs' and 'sm' layouts */
|
|
7
|
+
@media (max-width: 1232px) {
|
|
8
8
|
.footer-col-1, .footer-col-2, .footer-col-3 {
|
|
9
9
|
text-align: center !important;
|
|
10
10
|
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<footer class="footer" id="footer">
|
|
20
20
|
<div class="container-fluid">
|
|
21
21
|
<div class="row">
|
|
22
|
-
<div class="col-xl-4 col-lg-4 col-md-
|
|
22
|
+
<div class="col-xl-4 col-lg-4 col-md-3 col-sm-12 col-xs-12 text-left footer-col-1 no-wrap">
|
|
23
23
|
{% if request.user.is_authenticated %}
|
|
24
24
|
<p class="text-muted">
|
|
25
25
|
{% if settings.BRANDING_FILEPATHS.logo and settings.BRANDING_POWERED_BY_URL %}<a href="{{ settings.BRANDING_POWERED_BY_URL }}"><img src="{% static 'img/nautobot_logo.svg' %}" height="20" /> Powered</a> ·{% endif %}
|
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
</p>
|
|
28
28
|
{% endif %}
|
|
29
29
|
</div>
|
|
30
|
-
<div class="col-xl-4 col-lg-
|
|
30
|
+
<div class="col-xl-4 col-lg-3 col-md-3 col-sm-12 col-xs-12 text-center footer-col-2">
|
|
31
31
|
<p class="text-muted">{% now 'Y-m-d H:i:s T' %}</p>
|
|
32
32
|
</div>
|
|
33
|
-
<div class="col-xl-4 col-lg-
|
|
33
|
+
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12 col-xs-12 text-right footer-col-3 noprint">
|
|
34
34
|
{% if request.user.is_authenticated %}
|
|
35
35
|
<p class="text-muted footer-links">
|
|
36
36
|
<a href="#theme_modal" data-toggle="modal" data-target="#theme_modal" id="btn-theme-modal"><i class="mdi mdi-theme-light-dark text-primary"></i>Theme</a> ·
|
|
@@ -121,9 +121,6 @@
|
|
|
121
121
|
document.addEventListener('DOMContentLoaded', function() {
|
|
122
122
|
const navbar = document.querySelector('.navbar-fixed-left');
|
|
123
123
|
const navbarHeader = document.querySelector('.navbar-header');
|
|
124
|
-
const breadcrumb = document.querySelector('.breadcrumb');
|
|
125
|
-
const bannerAlertArea = document.querySelector('.banner-alert-area');
|
|
126
|
-
const bannerAlert = document.querySelector('.plugin-banner') !== null;
|
|
127
124
|
const mainContent = document.querySelector('#main-content');
|
|
128
125
|
const footer = document.querySelector('#footer');
|
|
129
126
|
const dropdownToggles = document.querySelectorAll('.navbar-fixed-left .navbar-nav > .dropdown > a[data-toggle="collapse"]');
|
|
@@ -180,10 +177,6 @@
|
|
|
180
177
|
function adjustElementsForNavbarState(isCollapsed) {
|
|
181
178
|
const marginLeftValue = isCollapsed ? '-240px' : '0px';
|
|
182
179
|
mainContent.style.marginLeft = marginLeftValue;
|
|
183
|
-
bannerAlertArea.classList.toggle('collapsed', isCollapsed);
|
|
184
|
-
if(!bannerAlert && breadcrumb) {
|
|
185
|
-
breadcrumb.classList.toggle('collapsed', isCollapsed);
|
|
186
|
-
};
|
|
187
180
|
if(footer) footer.style.marginLeft = marginLeftValue;
|
|
188
181
|
toggler.style.left = isCollapsed ? '-5px' : '225px';
|
|
189
182
|
}
|
|
@@ -37,6 +37,12 @@ from nautobot.core.settings_funcs import is_truthy, parse_redis_connection
|
|
|
37
37
|
# Number of seconds to cache ContentType lookups. Set to 0 to disable caching.
|
|
38
38
|
# CONTENT_TYPE_CACHE_TIMEOUT = int(os.getenv("NAUTOBOT_CONTENT_TYPE_CACHE_TIMEOUT", "0"))
|
|
39
39
|
|
|
40
|
+
# Celery Beat heartbeat file path - will be touched by Beat each time it wakes up as a proof-of-health.
|
|
41
|
+
# CELERY_BEAT_HEARTBEAT_FILE = os.getenv(
|
|
42
|
+
# "NAUTOBOT_CELERY_BEAT_HEARTBEAT_FILE",
|
|
43
|
+
# os.path.join(tempfile.gettempdir(), "nautobot_celery_beat_heartbeat"),
|
|
44
|
+
# )
|
|
45
|
+
|
|
40
46
|
# Celery broker URL used to tell workers where queues are located
|
|
41
47
|
#
|
|
42
48
|
# CELERY_BROKER_URL = os.getenv("NAUTOBOT_CELERY_BROKER_URL", parse_redis_connection(redis_database=0))
|
|
@@ -7,13 +7,20 @@
|
|
|
7
7
|
{% include 'inc/media.html' %}
|
|
8
8
|
{% endblock bootstrap_theme %}
|
|
9
9
|
|
|
10
|
-
{% block navbar %}
|
|
11
|
-
{% include 'inc/nav_menu.html' %}
|
|
12
|
-
{% endblock navbar %}
|
|
13
|
-
|
|
14
10
|
{% block body %}
|
|
15
|
-
{
|
|
11
|
+
{% include 'inc/nav_menu.html' %}
|
|
12
|
+
<div class="container-fluid" id="main-content">
|
|
13
|
+
{{ block.super }}
|
|
14
|
+
</div>
|
|
16
15
|
{% include 'inc/footer.html' %}
|
|
16
|
+
<style>
|
|
17
|
+
.navbar-static-top {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
#main-content {
|
|
21
|
+
min-height: calc(100vh - 70px);
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
17
24
|
{% endblock body %}
|
|
18
25
|
|
|
19
26
|
{% block script %}
|
nautobot/core/testing/mixins.py
CHANGED
|
@@ -4,6 +4,7 @@ import warnings
|
|
|
4
4
|
from django.apps import apps
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
|
+
from django.core.cache import cache
|
|
7
8
|
from django.core.exceptions import FieldDoesNotExist
|
|
8
9
|
from django.db.models import JSONField, ManyToManyField
|
|
9
10
|
from django.forms.models import model_to_dict
|
|
@@ -13,7 +14,7 @@ from rest_framework.test import APIClient, APIRequestFactory
|
|
|
13
14
|
from nautobot.core import testing
|
|
14
15
|
from nautobot.core.models import fields as core_fields
|
|
15
16
|
from nautobot.core.utils import permissions
|
|
16
|
-
from nautobot.extras import management, models as extras_models
|
|
17
|
+
from nautobot.extras import management, models as extras_models
|
|
17
18
|
from nautobot.users import models as users_models
|
|
18
19
|
|
|
19
20
|
# Use the proper swappable User model
|
|
@@ -63,11 +64,18 @@ class NautobotTestCaseMixin:
|
|
|
63
64
|
self.client.force_login(self.user)
|
|
64
65
|
|
|
65
66
|
def tearDown(self):
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
"""
|
|
68
|
+
Clear cache after each test case.
|
|
69
|
+
|
|
70
|
+
In theory this shouldn't be necessary as our cache **should** appropriately update and clear itself when
|
|
71
|
+
data changes occur, but in practice we've seen issues here. Best guess at present is that it's due to
|
|
72
|
+
`TransactionTestCase` truncating the database, which presumably doesn't trigger the relevant Django signals
|
|
73
|
+
that would otherwise refresh the cache appropriately.
|
|
74
|
+
|
|
75
|
+
See also: https://code.djangoproject.com/ticket/11505
|
|
76
|
+
"""
|
|
70
77
|
super().tearDown()
|
|
78
|
+
cache.clear()
|
|
71
79
|
|
|
72
80
|
def prepare_instance(self, instance):
|
|
73
81
|
"""
|
|
@@ -15,47 +15,33 @@ class PluginNavBarTestCase(SeleniumTestCase):
|
|
|
15
15
|
|
|
16
16
|
fixtures = ["user-data.json"]
|
|
17
17
|
navbar = {
|
|
18
|
-
"Example Menu": {
|
|
19
|
-
"Example Group 1": {
|
|
20
|
-
"Example Model": {
|
|
21
|
-
"permission": "example_plugin.view_examplemodel",
|
|
22
|
-
"buttons": ["Add"],
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
18
|
"Circuits": {
|
|
27
19
|
"Circuits": {
|
|
28
20
|
"Circuits": {
|
|
29
|
-
"permission": "circuits.view_circuit",
|
|
30
21
|
"buttons": ["Add"],
|
|
31
22
|
},
|
|
32
23
|
"Circuit Types": {
|
|
33
|
-
"permission": "circuits.view_circuittype",
|
|
34
24
|
"buttons": ["Add"],
|
|
35
25
|
},
|
|
36
26
|
},
|
|
37
27
|
"Example Circuit Group": {
|
|
38
|
-
"Example
|
|
39
|
-
"permission": "example_plugin.view_examplemodel",
|
|
28
|
+
"Example Models": {
|
|
40
29
|
"buttons": ["Add"],
|
|
41
30
|
},
|
|
42
31
|
},
|
|
43
32
|
"Providers": {
|
|
44
33
|
"Providers": {
|
|
45
|
-
"permission": "circuits.view_provider",
|
|
46
34
|
"buttons": ["Add"],
|
|
47
35
|
},
|
|
48
36
|
},
|
|
49
37
|
},
|
|
50
38
|
"Plugins": {
|
|
51
39
|
"Example Nautobot App": {
|
|
52
|
-
"Models": {
|
|
53
|
-
"
|
|
54
|
-
"buttons": ["Add a new example model"],
|
|
40
|
+
"Example Models": {
|
|
41
|
+
"buttons": ["Add"],
|
|
55
42
|
},
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"buttons": [],
|
|
43
|
+
"Another Example Models": {
|
|
44
|
+
"buttons": ["Add"],
|
|
59
45
|
},
|
|
60
46
|
},
|
|
61
47
|
},
|
|
@@ -87,7 +73,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
|
|
|
87
73
|
|
|
88
74
|
group = tab.find_by_xpath(f"{tab_xpath}/following-sibling::ul//li[normalize-space()='Example Group 1']")
|
|
89
75
|
|
|
90
|
-
item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='Example
|
|
76
|
+
item_xpath = f"{tab_xpath}/following-sibling::ul//li[.//a[normalize-space()='Example Models']]"
|
|
91
77
|
group.find_by_xpath(item_xpath)
|
|
92
78
|
|
|
93
79
|
def test_plugin_navbar_modify_circuits(self):
|
|
@@ -147,7 +133,7 @@ class PluginNavBarTestCase(SeleniumTestCase):
|
|
|
147
133
|
for button_name in item_details["buttons"]:
|
|
148
134
|
button = item.find_by_xpath(f"{item_xpath}/div//a[@data-original-title='{button_name}']")
|
|
149
135
|
if button_class := getattr(ButtonActionColorChoices, button_name.upper(), None):
|
|
150
|
-
self.assertIn(button_class, button
|
|
136
|
+
self.assertIn(button_class, button["class"])
|
|
151
137
|
if button_icon := getattr(ButtonActionIconChoices, button_name.upper(), None):
|
|
152
138
|
icon = button.find_by_xpath(f"{item_xpath}/div//a[@data-original-title='{button_name}']/i")
|
|
153
139
|
self.assertIn(button_icon, icon["class"])
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from django.test import tag
|
|
2
|
+
|
|
3
|
+
from nautobot.core.testing import TestCase
|
|
4
|
+
from nautobot.core.utils.lookup import get_url_for_url_pattern, get_url_patterns
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@tag("integration")
|
|
8
|
+
class AuthenticationEnforcedTestCase(TestCase):
|
|
9
|
+
r"""
|
|
10
|
+
Test that all\* registered views require authentication to access.
|
|
11
|
+
|
|
12
|
+
\* with a very small number of known exceptions such as login and logout views.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def test_all_views_require_authentication(self):
|
|
16
|
+
self.client.logout()
|
|
17
|
+
url_patterns = get_url_patterns()
|
|
18
|
+
|
|
19
|
+
for url_pattern in url_patterns:
|
|
20
|
+
with self.subTest(url_pattern=url_pattern):
|
|
21
|
+
url = get_url_for_url_pattern(url_pattern)
|
|
22
|
+
response = self.client.get(url, follow=True)
|
|
23
|
+
|
|
24
|
+
if response.status_code == 405: # Method not allowed
|
|
25
|
+
response = self.client.post(url, follow=True)
|
|
26
|
+
|
|
27
|
+
# Is a view that *should* be open to unauthenticated users?
|
|
28
|
+
if url in [
|
|
29
|
+
"/admin/login/",
|
|
30
|
+
"/api/plugins/example-plugin/webhook/",
|
|
31
|
+
"/health/",
|
|
32
|
+
"/login/",
|
|
33
|
+
"/media-failure/",
|
|
34
|
+
"/template.css",
|
|
35
|
+
]:
|
|
36
|
+
self.assertHttpStatus(response, 200, msg=url)
|
|
37
|
+
elif response.status_code == 200:
|
|
38
|
+
# UI views generally should redirect unauthenticated users to the appropriate login page
|
|
39
|
+
if url.startswith("/admin"):
|
|
40
|
+
if "logout" in url:
|
|
41
|
+
# /admin/logout/ sets next=/admin/ because having login redirect to logout would be silly
|
|
42
|
+
redirect_url = "/admin/login/?next=/admin/"
|
|
43
|
+
else:
|
|
44
|
+
redirect_url = f"/admin/login/?next={url}"
|
|
45
|
+
else:
|
|
46
|
+
if "logout" in url:
|
|
47
|
+
# /logout/ sets next=/ because having login redirect back to logout would be silly
|
|
48
|
+
redirect_url = "/login/?next=/"
|
|
49
|
+
else:
|
|
50
|
+
redirect_url = f"/login/?next={url}"
|
|
51
|
+
self.assertRedirects(response, redirect_url)
|
|
52
|
+
elif response.status_code != 403:
|
|
53
|
+
if any(
|
|
54
|
+
url.startswith(path)
|
|
55
|
+
for path in [
|
|
56
|
+
"/complete/", # social auth
|
|
57
|
+
"/login/", # social auth
|
|
58
|
+
"/media/", # MEDIA_ROOT
|
|
59
|
+
"/plugins/example-plugin/docs/", # STATIC_ROOT
|
|
60
|
+
]
|
|
61
|
+
):
|
|
62
|
+
self.assertEqual(response.status_code, 404)
|
|
63
|
+
else:
|
|
64
|
+
self.fail(
|
|
65
|
+
f"Unexpected {response.status_code} response at {url}: "
|
|
66
|
+
+ response.content.decode(response.charset)
|
|
67
|
+
)
|
nautobot/core/tests/runner.py
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
1
3
|
from django.conf import settings
|
|
2
4
|
from django.core.management import call_command
|
|
3
5
|
from django.db import connections
|
|
4
|
-
from django.test.runner import DiscoverRunner
|
|
5
|
-
from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper
|
|
6
|
+
from django.test.runner import _init_worker, DiscoverRunner, ParallelTestSuite
|
|
7
|
+
from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper, override_settings
|
|
6
8
|
import yaml
|
|
7
9
|
|
|
8
10
|
from nautobot.core.celery import app, setup_nautobot_job_logging
|
|
11
|
+
from nautobot.core.settings_funcs import parse_redis_connection
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def init_worker_with_unique_cache(counter):
|
|
15
|
+
"""Extend Django's default parallel unit test setup to also ensure distinct Redis caches."""
|
|
16
|
+
_init_worker(counter) # call Django default to set _worker_id and set up parallel DB instances
|
|
17
|
+
# _worker_id is now 1, 2, 3, 4, etc.
|
|
18
|
+
|
|
19
|
+
from django.test.runner import _worker_id
|
|
20
|
+
|
|
21
|
+
# Redis DB indices 0 and 1 are used by non-automated testing, so we want to start at index 2
|
|
22
|
+
caches = copy.deepcopy(settings.CACHES)
|
|
23
|
+
caches["default"]["LOCATION"] = parse_redis_connection(redis_database=_worker_id + 1)
|
|
24
|
+
override_settings(CACHES=caches).enable()
|
|
25
|
+
print(f"Set settings.CACHES['default']['LOCATION'] to use Redis index {_worker_id + 1}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class NautobotParallelTestSuite(ParallelTestSuite):
|
|
29
|
+
init_worker = init_worker_with_unique_cache
|
|
9
30
|
|
|
10
31
|
|
|
11
32
|
class NautobotTestRunner(DiscoverRunner):
|
|
@@ -22,6 +43,8 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
22
43
|
Only integration tests that DO NOT inherit from `SeleniumTestCase` will need to be explicitly tagged.
|
|
23
44
|
"""
|
|
24
45
|
|
|
46
|
+
parallel_test_suite = NautobotParallelTestSuite
|
|
47
|
+
|
|
25
48
|
exclude_tags = ["integration"]
|
|
26
49
|
|
|
27
50
|
def __init__(self, cache_test_fixtures=False, **kwargs):
|
|
@@ -630,21 +630,9 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
|
|
|
630
630
|
self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
|
|
631
631
|
|
|
632
632
|
def test_graphql_api_no_token(self):
|
|
633
|
-
"""Validate unauthenticated users are not able to query anything
|
|
633
|
+
"""Validate unauthenticated users are not able to query anything."""
|
|
634
634
|
response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
|
|
635
|
-
self.assertEqual(response.status_code, status.
|
|
636
|
-
self.assertIsInstance(response.data["data"]["racks"], list)
|
|
637
|
-
names = [item["name"] for item in response.data["data"]["racks"]]
|
|
638
|
-
self.assertEqual(names, [])
|
|
639
|
-
|
|
640
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
641
|
-
def test_graphql_api_no_token_exempt(self):
|
|
642
|
-
"""Validate unauthenticated users are able to query based on the exempt permissions."""
|
|
643
|
-
response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
|
|
644
|
-
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
645
|
-
self.assertIsInstance(response.data["data"]["racks"], list)
|
|
646
|
-
names = [item["name"] for item in response.data["data"]["racks"]]
|
|
647
|
-
self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
|
|
635
|
+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
648
636
|
|
|
649
637
|
def test_graphql_api_wrong_token(self):
|
|
650
638
|
"""Validate a wrong token return 403."""
|
|
@@ -113,7 +113,7 @@ class NaturalKeyTestCase(BaseModelTest):
|
|
|
113
113
|
"""
|
|
114
114
|
|
|
115
115
|
# Ensure the cache is empty from previous tests
|
|
116
|
-
cache.delete(
|
|
116
|
+
cache.delete(self.FakeBaseModel._content_type_cache_key)
|
|
117
117
|
|
|
118
118
|
with patch.object(self.FakeBaseModel, "_content_type", return_value=True) as mock__content_type:
|
|
119
119
|
self.FakeBaseModel._content_type_cached
|
|
@@ -127,7 +127,7 @@ class NaturalKeyTestCase(BaseModelTest):
|
|
|
127
127
|
self.assertEqual(mock__content_type.call_count, 2)
|
|
128
128
|
|
|
129
129
|
# Clean-up after ourselves
|
|
130
|
-
cache.delete(
|
|
130
|
+
cache.delete(self.FakeBaseModel._content_type_cache_key)
|
|
131
131
|
|
|
132
132
|
@override_settings(CONTENT_TYPE_CACHE_TIMEOUT=0)
|
|
133
133
|
def test__content_type_caching_disabled(self):
|
|
@@ -136,7 +136,7 @@ class NaturalKeyTestCase(BaseModelTest):
|
|
|
136
136
|
"""
|
|
137
137
|
|
|
138
138
|
# Ensure the cache is empty from previous tests
|
|
139
|
-
cache.delete(
|
|
139
|
+
cache.delete(self.FakeBaseModel._content_type_cache_key)
|
|
140
140
|
|
|
141
141
|
with patch.object(self.FakeBaseModel, "_content_type", return_value=True) as mock__content_type:
|
|
142
142
|
self.FakeBaseModel._content_type_cached
|
|
@@ -1,13 +1,72 @@
|
|
|
1
1
|
from unittest.mock import patch
|
|
2
2
|
|
|
3
|
-
from django.test import TestCase
|
|
3
|
+
from django.test import tag, TestCase
|
|
4
|
+
from django.urls import resolve
|
|
4
5
|
|
|
5
6
|
from nautobot.core.apps import NAV_CONTEXT_NAMES, NavContext, NavGrouping, NavItem, register_new_ui_menu_items
|
|
7
|
+
from nautobot.core.choices import ButtonActionColorChoices, ButtonActionIconChoices
|
|
8
|
+
from nautobot.core.templatetags.helpers import bettertitle
|
|
9
|
+
from nautobot.core.utils.lookup import get_route_for_model
|
|
10
|
+
from nautobot.core.utils.permissions import get_permission_for_model
|
|
11
|
+
from nautobot.extras.registry import registry
|
|
6
12
|
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
@tag("unit")
|
|
15
|
+
class NavMenuTestCase(TestCase):
|
|
16
|
+
"""Verify correct construction of the nav menu."""
|
|
17
|
+
|
|
18
|
+
def test_menu_item_attributes(self):
|
|
19
|
+
"""Verify that menu items and buttons have the correct text and expected permissions."""
|
|
20
|
+
for tab in registry["nav_menu"]["tabs"]:
|
|
21
|
+
for group in registry["nav_menu"]["tabs"][tab]["groups"]:
|
|
22
|
+
for item_url, item_details in registry["nav_menu"]["tabs"][tab]["groups"][group]["items"].items():
|
|
23
|
+
with self.subTest(f"{tab} > {group} > {item_url}"):
|
|
24
|
+
view_func = resolve(item_url).func
|
|
25
|
+
try:
|
|
26
|
+
# NautobotUIViewSet
|
|
27
|
+
view_class = view_func.view_class
|
|
28
|
+
except AttributeError:
|
|
29
|
+
# ObjectListView
|
|
30
|
+
view_class = view_func.cls
|
|
31
|
+
try:
|
|
32
|
+
view_queryset = view_class.queryset
|
|
33
|
+
view_model = view_queryset.model
|
|
34
|
+
|
|
35
|
+
if item_details["name"] not in {
|
|
36
|
+
"Elevations",
|
|
37
|
+
"Interface Connections",
|
|
38
|
+
"Console Connections",
|
|
39
|
+
"Power Connections",
|
|
40
|
+
"Job Approval Queue",
|
|
41
|
+
}:
|
|
42
|
+
expected_name = bettertitle(view_model._meta.verbose_name_plural)
|
|
43
|
+
if expected_name == "VM Interfaces":
|
|
44
|
+
expected_name = "Interfaces"
|
|
45
|
+
elif expected_name == "Object Changes":
|
|
46
|
+
expected_name = "Change Log"
|
|
47
|
+
self.assertEqual(item_details["name"], expected_name)
|
|
48
|
+
if item_url == get_route_for_model(view_model, "list"):
|
|
49
|
+
# Not assertEqual as some menu items have additional permissions defined.
|
|
50
|
+
self.assertIn(get_permission_for_model(view_model, "view"), item_details["permissions"])
|
|
51
|
+
except AttributeError:
|
|
52
|
+
# Not a model view?
|
|
53
|
+
self.assertIn(item_details["name"], {"Installed Plugins", "Interface Connections"})
|
|
54
|
+
|
|
55
|
+
for button, button_details in item_details["buttons"].items():
|
|
56
|
+
with self.subTest(f"{tab} > {group} > {item_url} > {button}"):
|
|
57
|
+
# Currently all core menu items should have just a single Add button
|
|
58
|
+
self.assertEqual(button, "Add")
|
|
59
|
+
self.assertEqual(
|
|
60
|
+
button_details["permissions"], {get_permission_for_model(view_model, "add")}
|
|
61
|
+
)
|
|
62
|
+
self.assertEqual(button_details["link"], get_route_for_model(view_model, "add"))
|
|
63
|
+
self.assertEqual(button_details["button_class"], ButtonActionColorChoices.ADD)
|
|
64
|
+
self.assertEqual(button_details["icon_class"], ButtonActionIconChoices.ADD)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@tag("unit")
|
|
9
68
|
class NewUINavTest(TestCase):
|
|
10
|
-
@patch(
|
|
69
|
+
@patch.dict(registry, values={"new_ui_nav_menu": {}}, clear=True)
|
|
11
70
|
def test_build_new_ui_nav_menu(self):
|
|
12
71
|
"""Assert building and adding of new ui nav to registry
|
|
13
72
|
|
|
@@ -15,8 +74,6 @@ class NewUINavTest(TestCase):
|
|
|
15
74
|
1. New UI nav is added to registry["new_ui_nav_menu"]
|
|
16
75
|
2. registry["new_ui_nav_menu"] is sorted by weight
|
|
17
76
|
"""
|
|
18
|
-
from nautobot.core.apps import registry # Import here cause of the mock patch
|
|
19
|
-
|
|
20
77
|
# Test App 1
|
|
21
78
|
navigation_1 = (
|
|
22
79
|
NavContext(
|
|
@@ -32,7 +89,7 @@ class NewUINavTest(TestCase):
|
|
|
32
89
|
NavGrouping(
|
|
33
90
|
name="App 1 Inventory Group 2",
|
|
34
91
|
items=(
|
|
35
|
-
NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.
|
|
92
|
+
NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_role"]),
|
|
36
93
|
NavItem(name="Menu 2", link="extras:tag_list"),
|
|
37
94
|
),
|
|
38
95
|
),
|
|
@@ -62,7 +119,7 @@ class NewUINavTest(TestCase):
|
|
|
62
119
|
items=(
|
|
63
120
|
NavItem(name="Tags", link="extras:tag_list"),
|
|
64
121
|
NavItem(name="Location", link="dcim:location_list"),
|
|
65
|
-
NavItem(name="Roles", link="extras:role_list", permissions=["extras.
|
|
122
|
+
NavItem(name="Roles", link="extras:role_list", permissions=["extras.view_role"]),
|
|
66
123
|
),
|
|
67
124
|
),
|
|
68
125
|
),
|
|
@@ -100,7 +157,7 @@ class NewUINavTest(TestCase):
|
|
|
100
157
|
"Menu 1": {
|
|
101
158
|
"name": "Menu 1",
|
|
102
159
|
"weight": 1000,
|
|
103
|
-
"permissions": ["extras.
|
|
160
|
+
"permissions": ["extras.view_role"],
|
|
104
161
|
"data": "/extras/roles/",
|
|
105
162
|
},
|
|
106
163
|
"Menu 2": {
|
|
@@ -157,7 +214,7 @@ class NewUINavTest(TestCase):
|
|
|
157
214
|
"Roles": {
|
|
158
215
|
"name": "Roles",
|
|
159
216
|
"weight": 1000,
|
|
160
|
-
"permissions": ["extras.
|
|
217
|
+
"permissions": ["extras.view_role"],
|
|
161
218
|
"data": "/extras/roles/",
|
|
162
219
|
},
|
|
163
220
|
},
|
|
@@ -171,7 +228,7 @@ class NewUINavTest(TestCase):
|
|
|
171
228
|
def test_validation_in_new_ui_navigation_classes(self):
|
|
172
229
|
"""Test Validation on each of the new ui navigation classes `NavItem`, `NavGrouping`, `NavContext`"""
|
|
173
230
|
|
|
174
|
-
nav_item_1 = NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.
|
|
231
|
+
nav_item_1 = NavItem(name="Menu 1", link="extras:role_list", permissions=["extras.view_role"])
|
|
175
232
|
nav_item_2 = NavItem(name="Menu 2", link="invalid_url")
|
|
176
233
|
self.assertEqual(
|
|
177
234
|
nav_item_1.initial_dict,
|
|
@@ -105,7 +105,9 @@ class GetReleasesTestCase(SimpleTestCase):
|
|
|
105
105
|
# Check if result is put in cache
|
|
106
106
|
expected_version_str, expected_url = max(releases)
|
|
107
107
|
expected_version = version.parse(expected_version_str)
|
|
108
|
-
mock_cache_set.assert_called_once_with(
|
|
108
|
+
mock_cache_set.assert_called_once_with(
|
|
109
|
+
"nautobot.core.releases.get_latest_release", (expected_version, expected_url), 160876
|
|
110
|
+
)
|
|
109
111
|
|
|
110
112
|
@patch.object(requests, "get")
|
|
111
113
|
@patch.object(cache, "set")
|
|
@@ -142,7 +144,9 @@ class GetReleasesTestCase(SimpleTestCase):
|
|
|
142
144
|
# Check if result is put in cache
|
|
143
145
|
expected_version_str, expected_url = max(releases)
|
|
144
146
|
expected_version = version.parse(expected_version_str)
|
|
145
|
-
mock_cache_set.assert_called_once_with(
|
|
147
|
+
mock_cache_set.assert_called_once_with(
|
|
148
|
+
"nautobot.core.releases.get_latest_release", (expected_version, expected_url), 160876
|
|
149
|
+
)
|
|
146
150
|
|
|
147
151
|
@patch.object(requests, "get")
|
|
148
152
|
@patch.object(cache, "set")
|
|
@@ -172,7 +176,9 @@ class GetReleasesTestCase(SimpleTestCase):
|
|
|
172
176
|
)
|
|
173
177
|
|
|
174
178
|
# Check if failure is put in cache
|
|
175
|
-
mock_cache_set.assert_called_once_with(
|
|
179
|
+
mock_cache_set.assert_called_once_with(
|
|
180
|
+
"nautobot.core.releases.get_releases.no_retry", "https://localhost/unittest/releases", 900
|
|
181
|
+
)
|
|
176
182
|
|
|
177
183
|
@patch.object(requests, "get")
|
|
178
184
|
@patch.object(cache, "set")
|