nautobot 2.3.3__py3-none-any.whl → 2.3.5__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/core/celery/schedulers.py +2 -2
- nautobot/core/settings.py +3 -1
- nautobot/core/settings.yaml +40 -23
- nautobot/core/tests/runner.py +13 -6
- nautobot/core/tests/test_settings_schema.py +7 -3
- nautobot/core/tests/test_views.py +40 -1
- nautobot/core/views/generic.py +15 -15
- nautobot/core/views/mixins.py +12 -1
- nautobot/core/views/renderers.py +3 -1
- nautobot/core/views/utils.py +1 -1
- nautobot/dcim/api/serializers.py +1 -0
- nautobot/dcim/api/views.py +2 -0
- nautobot/dcim/forms.py +1 -1
- nautobot/dcim/tables/power.py +1 -1
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +1 -1
- nautobot/dcim/tests/test_api.py +58 -4
- nautobot/dcim/tests/test_filters.py +1 -1
- nautobot/dcim/tests/test_models.py +0 -2
- nautobot/dcim/views.py +5 -2
- nautobot/extras/api/views.py +9 -0
- nautobot/extras/models/jobs.py +9 -1
- nautobot/extras/querysets.py +10 -1
- nautobot/extras/tables.py +3 -0
- nautobot/extras/tests/test_api.py +36 -0
- nautobot/extras/tests/test_views.py +76 -1
- nautobot/extras/views.py +10 -7
- nautobot/ipam/forms.py +6 -1
- nautobot/ipam/models.py +5 -11
- nautobot/ipam/navigation.py +8 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/tests/test_filters.py +1 -1
- nautobot/ipam/tests/test_views.py +41 -41
- nautobot/ipam/views.py +1 -1
- nautobot/project-static/docs/404.html +60 -148
- nautobot/project-static/docs/additional-features/caching.html +3 -3
- nautobot/project-static/docs/additional-features/healthcheck.html +3 -3
- nautobot/project-static/docs/apps/index.html +60 -148
- nautobot/project-static/docs/apps/nautobot-apps.html +60 -148
- nautobot/project-static/docs/assets/javascripts/workers/{search.07f07601.min.js → search.6ce7567c.min.js} +3 -3
- nautobot/project-static/docs/assets/javascripts/workers/{search.07f07601.min.js.map → search.6ce7567c.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +60 -148
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +60 -148
- nautobot/project-static/docs/configuration/optional-settings.html +3 -3
- nautobot/project-static/docs/configuration/required-settings.html +3 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +60 -148
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +60 -148
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +60 -148
- nautobot/project-static/docs/development/apps/api/models/global-search.html +60 -148
- nautobot/project-static/docs/development/apps/api/models/graphql.html +60 -148
- nautobot/project-static/docs/development/apps/api/models/index.html +60 -148
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +60 -148
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +62 -150
- nautobot/project-static/docs/development/apps/api/prometheus.html +60 -148
- nautobot/project-static/docs/development/apps/api/setup.html +60 -148
- nautobot/project-static/docs/development/apps/api/testing.html +61 -149
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +60 -148
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +60 -148
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +60 -148
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +60 -148
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/base-template.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/index.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/notes.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +60 -148
- nautobot/project-static/docs/development/apps/api/views/urls.html +60 -148
- nautobot/project-static/docs/development/apps/index.html +61 -149
- nautobot/project-static/docs/development/apps/migration/code-updates.html +60 -148
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +60 -148
- nautobot/project-static/docs/development/apps/migration/from-v1.html +60 -148
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +60 -148
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +60 -148
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +60 -148
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +60 -148
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +60 -148
- nautobot/project-static/docs/development/core/application-registry.html +60 -148
- nautobot/project-static/docs/development/core/best-practices.html +60 -148
- nautobot/project-static/docs/development/core/bootstrap-ui.html +60 -148
- nautobot/project-static/docs/development/core/caching.html +60 -148
- nautobot/project-static/docs/development/core/controllers.html +60 -148
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +60 -148
- nautobot/project-static/docs/development/core/generic-views.html +60 -148
- nautobot/project-static/docs/development/core/getting-started.html +85 -169
- nautobot/project-static/docs/development/core/homepage.html +60 -148
- nautobot/project-static/docs/development/core/index.html +60 -148
- nautobot/project-static/docs/development/core/model-checklist.html +60 -148
- nautobot/project-static/docs/development/core/model-features.html +60 -148
- nautobot/project-static/docs/development/core/natural-keys.html +60 -148
- nautobot/project-static/docs/development/core/navigation-menu.html +60 -148
- nautobot/project-static/docs/development/core/release-checklist.html +60 -148
- nautobot/project-static/docs/development/core/role-internals.html +60 -148
- nautobot/project-static/docs/development/core/settings.html +60 -148
- nautobot/project-static/docs/development/core/style-guide.html +60 -148
- nautobot/project-static/docs/development/core/templates.html +60 -148
- nautobot/project-static/docs/development/core/testing.html +72 -152
- nautobot/project-static/docs/development/core/user-preferences.html +60 -148
- nautobot/project-static/docs/development/index.html +60 -148
- nautobot/project-static/docs/development/jobs/index.html +68 -156
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +61 -149
- nautobot/project-static/docs/docker/index.html +3 -3
- nautobot/project-static/docs/index.html +60 -148
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +3 -3
- nautobot/project-static/docs/overview/application_stack.html +60 -148
- nautobot/project-static/docs/overview/design_philosophy.html +60 -148
- nautobot/project-static/docs/release-notes/index.html +60 -148
- nautobot/project-static/docs/release-notes/version-1.0.html +60 -148
- nautobot/project-static/docs/release-notes/version-1.1.html +61 -149
- nautobot/project-static/docs/release-notes/version-1.2.html +63 -151
- nautobot/project-static/docs/release-notes/version-1.3.html +61 -149
- nautobot/project-static/docs/release-notes/version-1.4.html +62 -150
- nautobot/project-static/docs/release-notes/version-1.5.html +62 -150
- nautobot/project-static/docs/release-notes/version-1.6.html +64 -152
- nautobot/project-static/docs/release-notes/version-2.0.html +61 -149
- nautobot/project-static/docs/release-notes/version-2.1.html +62 -150
- nautobot/project-static/docs/release-notes/version-2.2.html +60 -148
- nautobot/project-static/docs/release-notes/version-2.3.html +475 -236
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +277 -285
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +62 -150
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +61 -149
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +62 -150
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +71 -169
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +13 -12524
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +8966 -0
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +13 -9218
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +12734 -0
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -152
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +13 -9108
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +61 -149
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +9491 -0
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9478 -0
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +13 -8833
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -152
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +61 -149
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +61 -149
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +60 -148
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +60 -148
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +8978 -0
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +60 -148
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +63 -151
- nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +61 -151
- nautobot/project-static/docs/user-guide/administration/installation/index.html +62 -150
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -148
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +76 -161
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/services.html +62 -150
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +13 -9577
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +13 -9560
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +13 -9064
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +62 -150
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +60 -148
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +66 -154
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +66 -154
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +63 -151
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +65 -153
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +63 -151
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +61 -149
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +61 -149
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +60 -148
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +61 -149
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +60 -148
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +60 -148
- nautobot/project-static/docs/user-guide/index.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +62 -150
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +61 -149
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +62 -150
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +60 -148
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +60 -148
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/tables.py +2 -2
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/METADATA +2 -2
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/RECORD +332 -328
- nautobot/project-static/docs/user-guide/administration/configuration/render-settings-fragment.j2 +0 -76
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/NOTICE +0 -0
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/WHEEL +0 -0
- {nautobot-2.3.3.dist-info → nautobot-2.3.5.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections.abc import Mapping
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
3
|
import logging
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
@@ -83,7 +83,7 @@ class NautobotScheduleEntry(ModelEntry):
|
|
|
83
83
|
# This will trigger the job to run at start_time
|
|
84
84
|
# and avoid the heap block.
|
|
85
85
|
if model.start_time:
|
|
86
|
-
model.last_run_at = model.last_run_at -
|
|
86
|
+
model.last_run_at = model.last_run_at - timedelta(days=365 * 30)
|
|
87
87
|
|
|
88
88
|
self.last_run_at = model.last_run_at
|
|
89
89
|
|
nautobot/core/settings.py
CHANGED
|
@@ -641,6 +641,8 @@ LOGIN_REDIRECT_URL = "home"
|
|
|
641
641
|
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
|
|
642
642
|
CONSTANCE_DATABASE_PREFIX = "constance:nautobot:"
|
|
643
643
|
CONSTANCE_DATABASE_CACHE_BACKEND = "default"
|
|
644
|
+
# Constance defaults to a 24-hour timeout when autofilling its cache, which is undesirable in many cases.
|
|
645
|
+
CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT = int(os.getenv("NAUTOBOT_CACHES_TIMEOUT", "300"))
|
|
644
646
|
CONSTANCE_IGNORE_ADMIN_VERSION_CHECK = True # avoid potential errors in a multi-node deployment
|
|
645
647
|
|
|
646
648
|
CONSTANCE_ADDITIONAL_FIELDS = {
|
|
@@ -853,7 +855,7 @@ CACHES = {
|
|
|
853
855
|
"django_prometheus.cache.backends.redis.RedisCache" if METRICS_ENABLED else "django_redis.cache.RedisCache",
|
|
854
856
|
),
|
|
855
857
|
"LOCATION": parse_redis_connection(redis_database=1),
|
|
856
|
-
"TIMEOUT": 300,
|
|
858
|
+
"TIMEOUT": int(os.getenv("NAUTOBOT_CACHES_TIMEOUT", "300")),
|
|
857
859
|
"OPTIONS": {
|
|
858
860
|
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
|
859
861
|
"PASSWORD": "",
|
nautobot/core/settings.yaml
CHANGED
|
@@ -47,7 +47,7 @@ properties:
|
|
|
47
47
|
description: >-
|
|
48
48
|
A list of valid fully-qualified domain names (FQDNs) and/or IP addresses that can be used to reach the
|
|
49
49
|
Nautobot service. (If provided as an environment variable, it should be a space-separated string, for example
|
|
50
|
-
`NAUTOBOT_ALLOWED_HOSTS="localhost 127.0.0.1 example.com"`)
|
|
50
|
+
`NAUTOBOT_ALLOWED_HOSTS="localhost 127.0.0.1 nautobot.example.com"`)
|
|
51
51
|
details: |-
|
|
52
52
|
Usually this is the same as the hostname for the Nautobot server, but can also be different; for example,
|
|
53
53
|
when using a reverse proxy serving the Nautobot website under a different FQDN than the hostname of the
|
|
@@ -70,7 +70,7 @@ properties:
|
|
|
70
70
|
|
|
71
71
|
!!! tip
|
|
72
72
|
If there is more than one hostname in this list, you may also need to set
|
|
73
|
-
[CSRF_TRUSTED_ORIGINS](
|
|
73
|
+
[CSRF_TRUSTED_ORIGINS](settings.md#csrf_trusted_origins) as well.
|
|
74
74
|
|
|
75
75
|
If you are not yet sure what the domain name and/or IP address of the Nautobot installation will be,
|
|
76
76
|
and are comfortable accepting the risks in doing so, you can set this to a wildcard (asterisk) to
|
|
@@ -83,7 +83,6 @@ properties:
|
|
|
83
83
|
!!! warning
|
|
84
84
|
It is not recommended to leave this value as `['*']` for production deployments.
|
|
85
85
|
environment_variable: "NAUTOBOT_ALLOWED_HOSTS"
|
|
86
|
-
is_required_setting: true
|
|
87
86
|
items:
|
|
88
87
|
type: "string"
|
|
89
88
|
see_also:
|
|
@@ -297,19 +296,21 @@ properties:
|
|
|
297
296
|
TIMEOUT: 300
|
|
298
297
|
description: "The `CACHES` setting is required to simplify the configuration for `django-redis`."
|
|
299
298
|
details: |-
|
|
300
|
-
The [`django-redis`](https://github.com/jazzband/django-redis) Django plugin is used to enable Redis
|
|
301
|
-
|
|
299
|
+
The [`django-redis`](https://github.com/jazzband/django-redis) Django plugin is used to enable Redis as a
|
|
300
|
+
concurrent write lock for preventing race conditions when allocating IP address objects.
|
|
301
|
+
Nautobot also uses the built-in [Django cache framework](https://docs.djangoproject.com/en/stable/topics/cache/)
|
|
302
|
+
(which also relies on the `CACHES` setting) to perform caching. This includes caching of the values of
|
|
303
|
+
[administratively configurable settings](#administratively-configurable-settings) as stored in the database.
|
|
302
304
|
|
|
303
|
-
!!!
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
is_required_setting: true
|
|
305
|
+
!!! tip
|
|
306
|
+
Rather than directly setting `CACHES["default"]["LOCATION"]`, we recommend managing this setting via
|
|
307
|
+
the various `NAUTOBOT_REDIS_*` environment variables, as those variables apply to both `CACHES` and
|
|
308
|
+
[`CELERY_BROKER_URL`](#celery_broker_url) alike, which is typically preferable.
|
|
309
|
+
|
|
310
|
+
+++ 2.3.4 "`NAUTOBOT_CACHES_TIMEOUT` environment variable"
|
|
311
|
+
Added support for the environment variable `NAUTOBOT_CACHES_TIMEOUT` for configuring the
|
|
312
|
+
`CACHES["default"]["TIMEOUT"]` setting. This environment variable also controls the cache timeout
|
|
313
|
+
for administratively configurable settings.
|
|
313
314
|
properties:
|
|
314
315
|
default:
|
|
315
316
|
properties:
|
|
@@ -322,6 +323,13 @@ properties:
|
|
|
322
323
|
type: "string"
|
|
323
324
|
LOCATION:
|
|
324
325
|
default: "redis://localhost:6379/1"
|
|
326
|
+
environment_variables:
|
|
327
|
+
- "NAUTOBOT_REDIS_SCHEME"
|
|
328
|
+
- "NAUTOBOT_REDIS_SSL"
|
|
329
|
+
- "NAUTOBOT_REDIS_USERNAME"
|
|
330
|
+
- "NAUTOBOT_REDIS_PASSWORD"
|
|
331
|
+
- "NAUTOBOT_REDIS_HOST"
|
|
332
|
+
- "NAUTOBOT_REDIS_PORT"
|
|
325
333
|
format: "uri"
|
|
326
334
|
type: "string"
|
|
327
335
|
OPTIONS:
|
|
@@ -336,10 +344,11 @@ properties:
|
|
|
336
344
|
type: "object"
|
|
337
345
|
TIMEOUT:
|
|
338
346
|
default: 300
|
|
347
|
+
environment_variable: "NAUTOBOT_CACHES_TIMEOUT"
|
|
339
348
|
type: "integer"
|
|
340
349
|
type: "object"
|
|
341
350
|
see_also:
|
|
342
|
-
"Guide to Nautobot
|
|
351
|
+
"Guide to Nautobot Redis configuration, including TLS and HA configuration": "redis.md"
|
|
343
352
|
type: "object"
|
|
344
353
|
CELERY_BEAT_HEARTBEAT_FILE:
|
|
345
354
|
default: "/tmp/nautobot_celery_beat_heartbeat"
|
|
@@ -351,8 +360,7 @@ properties:
|
|
|
351
360
|
default: {}
|
|
352
361
|
description: "A dict of additional options passed to the Celery broker transport."
|
|
353
362
|
details: >-
|
|
354
|
-
This is only required when
|
|
355
|
-
[configuring Celery to utilize Redis Sentinel](../../administration/guides/caching.md#celery-sentinel-configuration).
|
|
363
|
+
This is only required when [configuring Celery to utilize Redis Sentinel](redis.md#celery-sentinel-configuration).
|
|
356
364
|
properties:
|
|
357
365
|
master_name:
|
|
358
366
|
type: "string"
|
|
@@ -366,7 +374,18 @@ properties:
|
|
|
366
374
|
CELERY_BROKER_URL:
|
|
367
375
|
default: "redis://localhost:6379/0"
|
|
368
376
|
description: "Celery broker URL used to tell workers where queues are located."
|
|
369
|
-
|
|
377
|
+
details: >-
|
|
378
|
+
If the `NAUTOBOT_CELERY_BROKER_URL` environment variable is not set, the default for this setting will be
|
|
379
|
+
influenced by the various `NAUTOBOT_REDIS_*` environment variables instead, which is often preferable as those
|
|
380
|
+
variables also influence the [`CACHES`](#caches) configuration as well.
|
|
381
|
+
environment_variables:
|
|
382
|
+
- "NAUTOBOT_CELERY_BROKER_URL"
|
|
383
|
+
- "NAUTOBOT_REDIS_SCHEME"
|
|
384
|
+
- "NAUTOBOT_REDIS_SSL"
|
|
385
|
+
- "NAUTOBOT_REDIS_USERNAME"
|
|
386
|
+
- "NAUTOBOT_REDIS_PASSWORD"
|
|
387
|
+
- "NAUTOBOT_REDIS_HOST"
|
|
388
|
+
- "NAUTOBOT_REDIS_PORT"
|
|
370
389
|
format: "uri"
|
|
371
390
|
type: "string"
|
|
372
391
|
CELERY_BROKER_USE_SSL:
|
|
@@ -628,7 +647,6 @@ properties:
|
|
|
628
647
|
|
|
629
648
|
As of Nautobot 1.1.5 and later, if you have generated a new `nautobot_config.py` using
|
|
630
649
|
`nautobot-server init`, this line is already present in your config and no action is required.
|
|
631
|
-
is_required_setting: true
|
|
632
650
|
properties:
|
|
633
651
|
default:
|
|
634
652
|
additionalProperties: true
|
|
@@ -828,7 +846,7 @@ properties:
|
|
|
828
846
|
FORCE_SCRIPT_NAME:
|
|
829
847
|
default: null
|
|
830
848
|
description: >-
|
|
831
|
-
If not None, this will be used as the value of the SCRIPT_NAME environment variable in any HTTP request.
|
|
849
|
+
If not None, this will be used as the value of the `SCRIPT_NAME` environment variable in any HTTP request.
|
|
832
850
|
details: |-
|
|
833
851
|
This setting can be used to override the server-provided value of `SCRIPT_NAME`, which is most commonly used
|
|
834
852
|
for hosting Nautobot in a subdirectory (e.g. _example.com/nautobot/_).
|
|
@@ -1205,7 +1223,7 @@ properties:
|
|
|
1205
1223
|
!!! note
|
|
1206
1224
|
The Docker container normally attempts to run migrations on startup; however, if the database is
|
|
1207
1225
|
in a read-only state the Docker container will fail to start. Setting the environment variable
|
|
1208
|
-
[`NAUTOBOT_DOCKER_SKIP_INIT`](../
|
|
1226
|
+
[`NAUTOBOT_DOCKER_SKIP_INIT`](../guides/docker.md#nautobot_docker_skip_init) to `true`
|
|
1209
1227
|
will prevent the migrations from occurring.
|
|
1210
1228
|
|
|
1211
1229
|
!!! note
|
|
@@ -1609,7 +1627,6 @@ properties:
|
|
|
1609
1627
|
In the case of a highly available installation with multiple web servers, `SECRET_KEY` must be identical
|
|
1610
1628
|
among all servers in order to maintain a persistent user session state.
|
|
1611
1629
|
environment_variable: "NAUTOBOT_SECRET_KEY"
|
|
1612
|
-
is_required_setting: true
|
|
1613
1630
|
type: "string"
|
|
1614
1631
|
SESSION_CACHE_ALIAS:
|
|
1615
1632
|
default: "default"
|
nautobot/core/tests/runner.py
CHANGED
|
@@ -64,9 +64,16 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
64
64
|
action="store_true",
|
|
65
65
|
help="Save test database to a json fixture file to re-use on subsequent tests.",
|
|
66
66
|
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"--no-reusedb",
|
|
69
|
+
action="store_false",
|
|
70
|
+
dest="reusedb",
|
|
71
|
+
help="Supplement to --keepdb; if --no-reusedb is set an existing database will NOT be reused.",
|
|
72
|
+
)
|
|
67
73
|
|
|
68
|
-
def __init__(self, cache_test_fixtures=False, **kwargs):
|
|
74
|
+
def __init__(self, cache_test_fixtures=False, reusedb=True, **kwargs):
|
|
69
75
|
self.cache_test_fixtures = cache_test_fixtures
|
|
76
|
+
self.reusedb = reusedb
|
|
70
77
|
|
|
71
78
|
# Assert "integration" hasn't been provided w/ --tag
|
|
72
79
|
incoming_tags = kwargs.get("tags") or []
|
|
@@ -118,7 +125,9 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
118
125
|
connection.creation.create_test_db(
|
|
119
126
|
verbosity=self.verbosity,
|
|
120
127
|
autoclobber=not self.interactive,
|
|
121
|
-
keepdb=self.keepdb
|
|
128
|
+
keepdb=self.keepdb
|
|
129
|
+
# Extra check added for Nautobot:
|
|
130
|
+
and self.reusedb,
|
|
122
131
|
serialize=connection.settings_dict["TEST"].get("SERIALIZE", True),
|
|
123
132
|
)
|
|
124
133
|
|
|
@@ -149,7 +158,7 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
149
158
|
verbosity=self.verbosity,
|
|
150
159
|
keepdb=self.keepdb
|
|
151
160
|
# Extra check added for Nautobot:
|
|
152
|
-
and
|
|
161
|
+
and self.reusedb,
|
|
153
162
|
)
|
|
154
163
|
|
|
155
164
|
# Configure all other connections as mirrors of the first one
|
|
@@ -179,9 +188,7 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
179
188
|
connection.creation.destroy_test_db(
|
|
180
189
|
suffix=str(index + 1),
|
|
181
190
|
verbosity=self.verbosity,
|
|
182
|
-
keepdb=self.keepdb
|
|
183
|
-
# Extra check added for Nautobot
|
|
184
|
-
and not settings.TEST_USE_FACTORIES,
|
|
191
|
+
keepdb=self.keepdb,
|
|
185
192
|
)
|
|
186
193
|
|
|
187
194
|
# Extra block added for Nautobot
|
|
@@ -35,10 +35,13 @@ SETTINGS_DOCUMENTATION_SCHEMA = {
|
|
|
35
35
|
"environment_variable": {
|
|
36
36
|
"type": "string",
|
|
37
37
|
},
|
|
38
|
-
"
|
|
39
|
-
"type": "
|
|
38
|
+
"environment_variables": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
},
|
|
40
43
|
},
|
|
41
|
-
"
|
|
44
|
+
"is_constance_config": {
|
|
42
45
|
"type": "boolean",
|
|
43
46
|
},
|
|
44
47
|
"see_also": {
|
|
@@ -122,6 +125,7 @@ class SettingsJSONSchemaTestCase(TestCase):
|
|
|
122
125
|
"CONSTANCE_BACKEND",
|
|
123
126
|
"CONSTANCE_CONFIG",
|
|
124
127
|
"CONSTANCE_CONFIG_FIELDSETS",
|
|
128
|
+
"CONSTANCE_DATABASE_CACHE_AUTOFILL_TIMEOUT",
|
|
125
129
|
"CONSTANCE_DATABASE_CACHE_BACKEND",
|
|
126
130
|
"CONSTANCE_DATABASE_PREFIX",
|
|
127
131
|
"CSRF_FAILURE_VIEW",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import re
|
|
2
|
-
from unittest import mock
|
|
2
|
+
from unittest import mock, skipIf
|
|
3
3
|
import urllib.parse
|
|
4
4
|
|
|
5
5
|
from django.apps import apps
|
|
6
|
+
from django.conf import settings
|
|
6
7
|
from django.contrib.contenttypes.models import ContentType
|
|
7
8
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
8
9
|
from django.test import override_settings, RequestFactory
|
|
@@ -572,3 +573,41 @@ class SilkUIAccessTestCase(TestCase):
|
|
|
572
573
|
|
|
573
574
|
# Check for success status code (e.g., 200)
|
|
574
575
|
self.assertEqual(response.status_code, 200)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class ExampleViewWithCustomPermissionsTest(TestCase):
|
|
579
|
+
@skipIf(
|
|
580
|
+
"example_app" not in settings.PLUGINS,
|
|
581
|
+
"example_app not in settings.PLUGINS",
|
|
582
|
+
)
|
|
583
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
584
|
+
def test_permission_classes_attribute_is_enforced(self):
|
|
585
|
+
"""
|
|
586
|
+
If example app is installed, check if the ViewWithCustomPermissions
|
|
587
|
+
is enforcing the permissions specified in its `permission_classes` attribute.
|
|
588
|
+
"""
|
|
589
|
+
# Test IsAuthenticated permission
|
|
590
|
+
self.add_permissions("example_app.view_examplemodel")
|
|
591
|
+
self.client.logout()
|
|
592
|
+
url = reverse("plugins:example_app:view_with_custom_permissions")
|
|
593
|
+
response = self.client.get(url, follow=True)
|
|
594
|
+
self.assertHttpStatus(response, 200)
|
|
595
|
+
response_body = response.content.decode(response.charset)
|
|
596
|
+
# check if the user is redirected to the login page
|
|
597
|
+
self.assertIn(f'<input type="hidden" name="next" value="{url}" />', response_body)
|
|
598
|
+
|
|
599
|
+
# Test IsAdmin permission
|
|
600
|
+
self.client.force_login(self.user)
|
|
601
|
+
response = self.client.get(url, follow=True)
|
|
602
|
+
self.assertHttpStatus(response, 403)
|
|
603
|
+
response_body = response.content.decode(response.charset)
|
|
604
|
+
# check if the users have to have the permission to access the page
|
|
605
|
+
self.assertIn("You do not have permission to access this page.", response_body)
|
|
606
|
+
|
|
607
|
+
# View should be successfully accessed
|
|
608
|
+
self.user.is_staff = True
|
|
609
|
+
self.user.save()
|
|
610
|
+
response = self.client.get(url)
|
|
611
|
+
self.assertHttpStatus(response, 200)
|
|
612
|
+
response_body = response.content.decode(response.charset)
|
|
613
|
+
self.assertIn("You are viewing a table of example models", response_body)
|
nautobot/core/views/generic.py
CHANGED
|
@@ -17,7 +17,7 @@ from django.db.models import ManyToManyField, ProtectedError, Q
|
|
|
17
17
|
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
18
18
|
from django.http import HttpResponse
|
|
19
19
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
20
|
-
from django.urls import reverse
|
|
20
|
+
from django.urls import resolve, reverse
|
|
21
21
|
from django.utils.encoding import iri_to_uri
|
|
22
22
|
from django.utils.html import format_html
|
|
23
23
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
@@ -205,17 +205,16 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
205
205
|
filter_form = None
|
|
206
206
|
hide_hierarchy_ui = False
|
|
207
207
|
clear_view = request.GET.get("clear_view", False)
|
|
208
|
+
resolved_path = resolve(request.path)
|
|
209
|
+
list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
|
|
208
210
|
|
|
209
211
|
# If the user clicks on the clear view button, we do not check for global or user defaults
|
|
210
212
|
if not clear_view and not request.GET.get("saved_view"):
|
|
211
213
|
# Check if there is a default for this view for this specific user
|
|
212
|
-
app_label, model_name = model._meta.label.split(".")
|
|
213
|
-
view_name = f"{app_label}:{model_name.lower()}_list"
|
|
214
|
-
|
|
215
214
|
if not isinstance(user, AnonymousUser):
|
|
216
215
|
try:
|
|
217
216
|
user_default_saved_view_pk = UserSavedViewAssociation.objects.get(
|
|
218
|
-
user=user, view_name=
|
|
217
|
+
user=user, view_name=list_url
|
|
219
218
|
).saved_view.pk
|
|
220
219
|
# Saved view should either belong to the user or be public
|
|
221
220
|
SavedView.objects.get(
|
|
@@ -229,7 +228,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
229
228
|
|
|
230
229
|
# Check if there is a global default for this view
|
|
231
230
|
try:
|
|
232
|
-
global_saved_view = SavedView.objects.get(view=
|
|
231
|
+
global_saved_view = SavedView.objects.get(view=list_url, is_global_default=True)
|
|
233
232
|
return redirect(reverse("extras:savedview", kwargs={"pk": global_saved_view.pk}))
|
|
234
233
|
except ObjectDoesNotExist:
|
|
235
234
|
pass
|
|
@@ -302,7 +301,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
302
301
|
table_config_form = None
|
|
303
302
|
current_saved_view = None
|
|
304
303
|
current_saved_view_pk = self.request.GET.get("saved_view", None)
|
|
305
|
-
list_url = validated_viewname(model, "list")
|
|
306
304
|
# We are not using .restrict(request.user, "view") here
|
|
307
305
|
# User should be able to see any saved view that he has the list view access to.
|
|
308
306
|
if user.has_perms(["extras.view_savedview"]):
|
|
@@ -315,15 +313,17 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
315
313
|
SavedView.objects.filter(view=list_url, owner=user).order_by("name").only("pk", "name")
|
|
316
314
|
)
|
|
317
315
|
saved_views = shared_saved_views | user_owned_saved_views
|
|
316
|
+
|
|
317
|
+
if current_saved_view_pk:
|
|
318
|
+
try:
|
|
319
|
+
# We are not using .restrict(request.user, "view") here
|
|
320
|
+
# User should be able to see any saved view that he has the list view access to.
|
|
321
|
+
current_saved_view = SavedView.objects.get(view=list_url, pk=current_saved_view_pk)
|
|
322
|
+
except ObjectDoesNotExist:
|
|
323
|
+
messages.error(request, f"Saved view {current_saved_view_pk} not found")
|
|
324
|
+
|
|
325
|
+
# Construct the objects table
|
|
318
326
|
if self.table:
|
|
319
|
-
# Construct the objects table
|
|
320
|
-
if current_saved_view_pk:
|
|
321
|
-
try:
|
|
322
|
-
# We are not using .restrict(request.user, "view") here
|
|
323
|
-
# User should be able to see any saved view that he has the list view access to.
|
|
324
|
-
current_saved_view = SavedView.objects.get(view=list_url, pk=current_saved_view_pk)
|
|
325
|
-
except ObjectDoesNotExist:
|
|
326
|
-
messages.error(request, f"Saved view {current_saved_view_pk} not found")
|
|
327
327
|
if self.request.GET.getlist("sort") or (
|
|
328
328
|
current_saved_view is not None and current_saved_view.config.get("sort_order")
|
|
329
329
|
):
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -234,6 +234,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
234
234
|
serializer_class = None
|
|
235
235
|
table_class = None
|
|
236
236
|
notes_form_class = NoteForm
|
|
237
|
+
permission_classes = []
|
|
237
238
|
|
|
238
239
|
def get_permissions_for_model(self, model, actions):
|
|
239
240
|
"""
|
|
@@ -267,7 +268,7 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
267
268
|
"""
|
|
268
269
|
user = self.request.user
|
|
269
270
|
permission_required = self.get_required_permission()
|
|
270
|
-
# Check that the user has been granted the required permission(s) one by one.
|
|
271
|
+
# Check that the user has been granted the required Nautobot-specific object permission(s) one by one.
|
|
271
272
|
# In case the permission has `message` or `code`` attribute, we want to include those information in the permission_denied error.
|
|
272
273
|
for permission in permission_required:
|
|
273
274
|
# If the user does not have the permission required, we raise DRF's `NotAuthenticated` or `PermissionDenied` exception
|
|
@@ -280,6 +281,16 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
280
281
|
code=getattr(permission, "code", None),
|
|
281
282
|
)
|
|
282
283
|
|
|
284
|
+
# Check for drf-specific permissions (IsAutheticated, etc) in permission_classes which is empty by default.
|
|
285
|
+
# self.get_permissions() iterates through permissions specified in the `permission_classes` attribute.
|
|
286
|
+
for permission in self.get_permissions():
|
|
287
|
+
# If the user does not have the permission required, we raise DRF's `NotAuthenticated` or `PermissionDenied` exception
|
|
288
|
+
# which will be handled by self.handle_no_permission() in the UI appropriately in the dispatch() method
|
|
289
|
+
if not permission.has_permission(request, self):
|
|
290
|
+
self.permission_denied(
|
|
291
|
+
request, message=getattr(permission, "message", None), code=getattr(permission, "code", None)
|
|
292
|
+
)
|
|
293
|
+
|
|
283
294
|
def dispatch(self, request, *args, **kwargs):
|
|
284
295
|
"""
|
|
285
296
|
Override the default dispatch() method to check permissions first.
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
5
5
|
from django.core.exceptions import ObjectDoesNotExist
|
|
6
6
|
from django.db.models import Q
|
|
7
7
|
from django.template import engines, loader
|
|
8
|
+
from django.urls import resolve
|
|
8
9
|
from django_tables2 import RequestConfig
|
|
9
10
|
from rest_framework import renderers
|
|
10
11
|
|
|
@@ -295,7 +296,8 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
295
296
|
# Construct valid actions for list view.
|
|
296
297
|
valid_actions = self.validate_action_buttons(view, request)
|
|
297
298
|
# Query SavedViews for dropdown button
|
|
298
|
-
|
|
299
|
+
resolved_path = resolve(request.path)
|
|
300
|
+
list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
|
|
299
301
|
saved_views = None
|
|
300
302
|
if model.is_saved_view_model:
|
|
301
303
|
# We are not using .restrict(request.user, "view") here
|
nautobot/core/views/utils.py
CHANGED
|
@@ -213,7 +213,7 @@ def handle_protectederror(obj_list, request, e):
|
|
|
213
213
|
protected_objects = list(e.protected_objects)
|
|
214
214
|
protected_count = len(protected_objects) if len(protected_objects) <= 50 else "More than 50"
|
|
215
215
|
err_message = format_html(
|
|
216
|
-
"Unable to delete <strong>{}</strong>. {} dependent objects were found: ",
|
|
216
|
+
str(e.args[0]) if e.args else "Unable to delete <strong>{}</strong>. {} dependent objects were found: ",
|
|
217
217
|
", ".join(str(obj) for obj in obj_list),
|
|
218
218
|
protected_count,
|
|
219
219
|
)
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -389,6 +389,7 @@ class RackElevationDetailFilterSerializer(serializers.Serializer):
|
|
|
389
389
|
expand_devices = serializers.BooleanField(required=False, default=True)
|
|
390
390
|
include_images = serializers.BooleanField(required=False, default=True)
|
|
391
391
|
display_fullname = serializers.BooleanField(required=False, default=True)
|
|
392
|
+
is_occupied = serializers.BooleanField(required=False, allow_null=True, default=None)
|
|
392
393
|
|
|
393
394
|
def validate(self, attrs):
|
|
394
395
|
attrs.setdefault("unit_width", get_settings_or_config("RACK_ELEVATION_DEFAULT_UNIT_WIDTH"))
|
nautobot/dcim/api/views.py
CHANGED
|
@@ -231,6 +231,8 @@ class RackViewSet(NautobotModelViewSet):
|
|
|
231
231
|
exclude=data["exclude"],
|
|
232
232
|
expand_devices=data["expand_devices"],
|
|
233
233
|
)
|
|
234
|
+
if data["is_occupied"] is not None:
|
|
235
|
+
elevation = [u for u in elevation if u["occupied"] == data["is_occupied"]]
|
|
234
236
|
|
|
235
237
|
# Enable filtering rack units by ID
|
|
236
238
|
q = data["q"]
|
nautobot/dcim/forms.py
CHANGED
|
@@ -1900,7 +1900,7 @@ class DeviceForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm, LocalC
|
|
|
1900
1900
|
widget=APISelect(
|
|
1901
1901
|
api_url="/api/dcim/racks/{{rack}}/elevation/",
|
|
1902
1902
|
attrs={
|
|
1903
|
-
"disabled-indicator": "
|
|
1903
|
+
"disabled-indicator": "occupied",
|
|
1904
1904
|
"data-query-param-face": '["$face"]',
|
|
1905
1905
|
},
|
|
1906
1906
|
),
|
nautobot/dcim/tables/power.py
CHANGED
|
@@ -29,7 +29,7 @@ class PowerPanelTable(BaseTable):
|
|
|
29
29
|
location = tables.Column(linkify=True)
|
|
30
30
|
power_feed_count = LinkedCountColumn(
|
|
31
31
|
viewname="dcim:powerfeed_list",
|
|
32
|
-
url_params={"
|
|
32
|
+
url_params={"power_panel": "pk"},
|
|
33
33
|
verbose_name="Feeds",
|
|
34
34
|
)
|
|
35
35
|
tags = TagColumn(url_name="dcim:powerpanel_list")
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
</tr>
|
|
17
17
|
<tr>
|
|
18
18
|
<td>Device Types</td>
|
|
19
|
-
<td><a href="{% url 'dcim:devicetype_list' %}?device_family={{ object.name }}">{{
|
|
19
|
+
<td><a href="{% url 'dcim:devicetype_list' %}?device_family={{ object.name }}">{{ device_type_count }}</a></td>
|
|
20
20
|
</tr>
|
|
21
21
|
<tr>
|
|
22
22
|
<td>Total Devices</td>
|
nautobot/dcim/tests/test_api.py
CHANGED
|
@@ -139,7 +139,7 @@ class Mixins:
|
|
|
139
139
|
super().setUpTestData()
|
|
140
140
|
cls.device_type = DeviceType.objects.first()
|
|
141
141
|
cls.manufacturer = cls.device_type.manufacturer
|
|
142
|
-
cls.location = Location.objects.filter(
|
|
142
|
+
cls.location = Location.objects.filter(location_type__name="Campus").first()
|
|
143
143
|
cls.device_role = Role.objects.get_for_model(Device).first()
|
|
144
144
|
cls.device_status = Status.objects.get_for_model(Device).first()
|
|
145
145
|
cls.device = Device.objects.create(
|
|
@@ -539,6 +539,8 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModel
|
|
|
539
539
|
def setUpTestData(cls):
|
|
540
540
|
cls.status = Status.objects.get_for_model(Location).first()
|
|
541
541
|
location_type = LocationType.objects.create(name="Location Type 1")
|
|
542
|
+
location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
|
|
543
|
+
|
|
542
544
|
cls.locations = (
|
|
543
545
|
Location.objects.create(name="Location 1", location_type=location_type, status=cls.status),
|
|
544
546
|
Location.objects.create(name="Location 2", location_type=location_type, status=cls.status),
|
|
@@ -548,8 +550,6 @@ class RackGroupTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModel
|
|
|
548
550
|
RackGroup.objects.create(location=cls.locations[1], name="Parent Rack Group 2"),
|
|
549
551
|
)
|
|
550
552
|
|
|
551
|
-
location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
|
|
552
|
-
|
|
553
553
|
RackGroup.objects.create(
|
|
554
554
|
location=cls.locations[0],
|
|
555
555
|
name="Rack Group 1",
|
|
@@ -643,7 +643,9 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|
|
643
643
|
|
|
644
644
|
@classmethod
|
|
645
645
|
def setUpTestData(cls):
|
|
646
|
-
locations = Location.objects.
|
|
646
|
+
locations = Location.objects.filter(devices__isnull=False)[:2]
|
|
647
|
+
for location in locations:
|
|
648
|
+
location.location_type.content_types.add(ContentType.objects.get_for_model(RackGroup))
|
|
647
649
|
|
|
648
650
|
rack_groups = (
|
|
649
651
|
RackGroup.objects.create(location=locations[0], name="Rack Group 1"),
|
|
@@ -675,6 +677,20 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|
|
675
677
|
status=statuses[0],
|
|
676
678
|
)
|
|
677
679
|
|
|
680
|
+
populated_rack = Rack.objects.create(
|
|
681
|
+
location=locations[0],
|
|
682
|
+
rack_group=rack_groups[0],
|
|
683
|
+
role=rack_roles[0],
|
|
684
|
+
name="Populated Rack",
|
|
685
|
+
status=statuses[0],
|
|
686
|
+
)
|
|
687
|
+
# Place a device in Rack 4
|
|
688
|
+
device = Device.objects.filter(location=populated_rack.location, rack=None).first()
|
|
689
|
+
device.rack = populated_rack
|
|
690
|
+
device.face = "front"
|
|
691
|
+
device.position = 10
|
|
692
|
+
device.save()
|
|
693
|
+
|
|
678
694
|
cls.create_data = [
|
|
679
695
|
{
|
|
680
696
|
"name": "Test Rack 4",
|
|
@@ -742,6 +758,44 @@ class RackTest(APIViewTestCases.APIViewTestCase):
|
|
|
742
758
|
response = self.client.get(url, params, **self.header)
|
|
743
759
|
self.assertHttpStatus(response, 200)
|
|
744
760
|
|
|
761
|
+
def test_filter_rack_elevation_is_occupied(self):
|
|
762
|
+
"""
|
|
763
|
+
Test filtering the list of rack elevations by occupied status.
|
|
764
|
+
"""
|
|
765
|
+
rack = Rack.objects.get(name="Populated Rack")
|
|
766
|
+
self.add_permissions("dcim.view_rack")
|
|
767
|
+
url = reverse("dcim-api:rack-elevation", kwargs={"pk": rack.pk})
|
|
768
|
+
# Get all units first
|
|
769
|
+
params = {"face": "front"}
|
|
770
|
+
response = self.client.get(url, params, **self.header)
|
|
771
|
+
all_units = response.data["results"]
|
|
772
|
+
# Assert the count is equal to the number of units in the rack
|
|
773
|
+
self.assertEqual(len(all_units), rack.u_height)
|
|
774
|
+
|
|
775
|
+
# Next get only unoccupied units
|
|
776
|
+
params = {"face": "front", "is_occupied": False}
|
|
777
|
+
response = self.client.get(url, params, **self.header)
|
|
778
|
+
unoccupied_units = response.data["results"]
|
|
779
|
+
# Assert the count is more than 0
|
|
780
|
+
self.assertGreater(len(unoccupied_units), 0)
|
|
781
|
+
# Assert the unoccupied count is less than the total number of units
|
|
782
|
+
self.assertLess(len(unoccupied_units), len(all_units))
|
|
783
|
+
|
|
784
|
+
# Next get only occupied units
|
|
785
|
+
params = {"face": "front", "is_occupied": True}
|
|
786
|
+
response = self.client.get(url, params, **self.header)
|
|
787
|
+
occupied_units = response.data["results"]
|
|
788
|
+
# Assert the count is more than 0
|
|
789
|
+
self.assertGreater(len(occupied_units), 0)
|
|
790
|
+
# Assert the occupied count is less than the total number of units
|
|
791
|
+
self.assertLess(len(occupied_units), len(all_units))
|
|
792
|
+
|
|
793
|
+
# Assert that the sum of unoccupied and occupied units is equal to the total number of units
|
|
794
|
+
self.assertEqual(len(unoccupied_units) + len(occupied_units), len(all_units))
|
|
795
|
+
# Assert that the lists are mutually exclusive
|
|
796
|
+
self.assertEqual(len([unit for unit in unoccupied_units if unit in occupied_units]), 0)
|
|
797
|
+
self.assertEqual(len([unit for unit in occupied_units if unit in unoccupied_units]), 0)
|
|
798
|
+
|
|
745
799
|
def test_get_rack_elevation_svg(self):
|
|
746
800
|
"""
|
|
747
801
|
GET a single rack elevation in SVG format.
|
|
@@ -3349,7 +3349,7 @@ class CableTestCase(FilterTestCases.FilterTestCase):
|
|
|
3349
3349
|
|
|
3350
3350
|
def test_device(self):
|
|
3351
3351
|
"""Test that the device filter returns all cables for a device and its modules."""
|
|
3352
|
-
interfaces = Interface.objects.filter(cable__isnull=True)[:3]
|
|
3352
|
+
interfaces = list(Interface.objects.filter(cable__isnull=True)[:3])
|
|
3353
3353
|
manufacturer = Manufacturer.objects.first()
|
|
3354
3354
|
device_type = DeviceType.objects.create(
|
|
3355
3355
|
manufacturer=manufacturer, model="Test Device Filter for Cable Device Type"
|
|
@@ -65,7 +65,6 @@ from nautobot.dcim.models import (
|
|
|
65
65
|
from nautobot.extras import context_managers
|
|
66
66
|
from nautobot.extras.choices import CustomFieldTypeChoices
|
|
67
67
|
from nautobot.extras.models import CustomField, Role, SecretsGroup, Status
|
|
68
|
-
from nautobot.ipam.factory import VLANGroupFactory
|
|
69
68
|
from nautobot.ipam.models import IPAddress, IPAddressToInterface, Namespace, Prefix, VLAN, VLANGroup
|
|
70
69
|
from nautobot.tenancy.models import Tenant
|
|
71
70
|
from nautobot.users.models import User
|
|
@@ -2351,7 +2350,6 @@ class InterfaceTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
|
|
|
2351
2350
|
vid=100,
|
|
2352
2351
|
location=location_2,
|
|
2353
2352
|
status=vlan_status,
|
|
2354
|
-
vlan_group=VLANGroupFactory.create(location=location_2),
|
|
2355
2353
|
)
|
|
2356
2354
|
|
|
2357
2355
|
cls.namespace = Namespace.objects.create(name="dcim_test_interface_ip_addresses")
|