nautobot 2.2.8__py3-none-any.whl → 2.2.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/core/filters.py +15 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +2 -3
- nautobot/core/jobs/__init__.py +4 -1
- nautobot/core/settings.py +13 -2
- nautobot/core/settings.yaml +14 -0
- nautobot/core/templates/nautobot_config.py.j2 +15 -0
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/test_jobs.py +82 -2
- nautobot/core/tests/test_templatetags_netutils.py +3 -3
- nautobot/dcim/models/device_components.py +7 -0
- nautobot/dcim/models/devices.py +4 -0
- nautobot/dcim/tables/devices.py +19 -4
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/tests/test_models.py +31 -0
- nautobot/dcim/tests/test_views.py +13 -0
- nautobot/dcim/views.py +8 -2
- nautobot/extras/api/views.py +7 -59
- nautobot/extras/homepage.py +12 -2
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/models/jobs.py +81 -0
- nautobot/extras/signals.py +14 -1
- nautobot/extras/tables.py +36 -5
- nautobot/extras/templates/extras/job_detail.html +11 -0
- nautobot/extras/tests/test_views.py +21 -0
- nautobot/extras/utils.py +34 -5
- nautobot/extras/views.py +20 -46
- nautobot/ipam/models.py +9 -12
- nautobot/ipam/tests/test_models.py +3 -2
- nautobot/ipam/views.py +2 -8
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +2 -2
- nautobot/project-static/docs/apps/index.html +2 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +4 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
- nautobot/project-static/docs/development/apps/api/setup.html +2 -2
- nautobot/project-static/docs/development/apps/api/testing.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
- nautobot/project-static/docs/development/apps/index.html +2 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
- nautobot/project-static/docs/development/core/application-registry.html +2 -2
- nautobot/project-static/docs/development/core/best-practices.html +2 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
- nautobot/project-static/docs/development/core/caching.html +2 -2
- nautobot/project-static/docs/development/core/controllers.html +2 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
- nautobot/project-static/docs/development/core/generic-views.html +2 -2
- nautobot/project-static/docs/development/core/getting-started.html +2 -2
- nautobot/project-static/docs/development/core/homepage.html +2 -2
- nautobot/project-static/docs/development/core/index.html +13 -2
- nautobot/project-static/docs/development/core/model-checklist.html +2 -2
- nautobot/project-static/docs/development/core/model-features.html +2 -2
- nautobot/project-static/docs/development/core/natural-keys.html +2 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
- nautobot/project-static/docs/development/core/release-checklist.html +2 -2
- nautobot/project-static/docs/development/core/role-internals.html +2 -2
- nautobot/project-static/docs/development/core/settings.html +2 -2
- nautobot/project-static/docs/development/core/style-guide.html +2 -2
- nautobot/project-static/docs/development/core/templates.html +2 -2
- nautobot/project-static/docs/development/core/testing.html +2 -2
- nautobot/project-static/docs/development/core/user-preferences.html +2 -2
- nautobot/project-static/docs/development/index.html +2 -2
- nautobot/project-static/docs/development/jobs/index.html +377 -363
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
- nautobot/project-static/docs/index.html +8228 -13
- nautobot/project-static/docs/overview/application_stack.html +2 -2
- nautobot/project-static/docs/overview/design_philosophy.html +4 -4
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +232 -95
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +260 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +34 -2
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +6 -2
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +60 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
- nautobot/project-static/docs/user-guide/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
- nautobot/virtualization/tables.py +2 -5
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/METADATA +2 -2
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/RECORD +299 -299
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/entry_points.txt +0 -0
nautobot/core/filters.py
CHANGED
|
@@ -7,9 +7,11 @@ from django import forms as django_forms
|
|
|
7
7
|
from django.conf import settings
|
|
8
8
|
from django.db import models
|
|
9
9
|
from django.forms.utils import ErrorDict, ErrorList
|
|
10
|
+
from django.utils.encoding import force_str
|
|
11
|
+
from django.utils.text import capfirst
|
|
10
12
|
import django_filters
|
|
11
13
|
from django_filters.constants import EMPTY_VALUES
|
|
12
|
-
from django_filters.utils import get_model_field, resolve_field
|
|
14
|
+
from django_filters.utils import get_model_field, label_for_filter, resolve_field, verbose_lookup_expr
|
|
13
15
|
from drf_spectacular.types import OpenApiTypes
|
|
14
16
|
from drf_spectacular.utils import extend_schema_field
|
|
15
17
|
|
|
@@ -681,6 +683,18 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
681
683
|
# Of course setting the negation of the existing filter's exclude attribute handles both cases
|
|
682
684
|
new_filter.exclude = not filter_field.exclude
|
|
683
685
|
|
|
686
|
+
# If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
|
|
687
|
+
# so we have to do it.
|
|
688
|
+
if filter_field.label and filter_field.label != label_for_filter(
|
|
689
|
+
cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
|
|
690
|
+
):
|
|
691
|
+
# Lightly adjusted from label_for_filter() implementation:
|
|
692
|
+
verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
|
|
693
|
+
if isinstance(lookup_expr, str):
|
|
694
|
+
verbose_expression.append(verbose_lookup_expr(lookup_expr))
|
|
695
|
+
verbose_expression = [force_str(part) for part in verbose_expression if part]
|
|
696
|
+
new_filter.label = capfirst(" ".join(verbose_expression))
|
|
697
|
+
|
|
684
698
|
magic_filters[new_filter_name] = new_filter
|
|
685
699
|
|
|
686
700
|
return magic_filters
|
|
@@ -64,8 +64,8 @@ def generate_filter_resolver(schema_type, resolver_name, field_name):
|
|
|
64
64
|
"""
|
|
65
65
|
filterset_class = schema_type._meta.filterset_class
|
|
66
66
|
|
|
67
|
-
def resolve_filter(self,
|
|
68
|
-
if not filterset_class:
|
|
67
|
+
def resolve_filter(self, info, **kwargs):
|
|
68
|
+
if not filterset_class or not kwargs:
|
|
69
69
|
return getattr(self, field_name).all()
|
|
70
70
|
|
|
71
71
|
# Inverse of substitution logic from get_filtering_args_from_filterset() - transform "_type" back to "type"
|
nautobot/core/graphql/schema.py
CHANGED
|
@@ -370,10 +370,9 @@ def extend_schema_type_relationships(schema_type, model):
|
|
|
370
370
|
"""Extend the schema type with attributes and resolvers corresponding
|
|
371
371
|
to the relationships associated with this model."""
|
|
372
372
|
|
|
373
|
-
ct = ContentType.objects.get_for_model(model)
|
|
374
373
|
relationships_by_side = {
|
|
375
|
-
"source": Relationship.objects.
|
|
376
|
-
"destination": Relationship.objects.
|
|
374
|
+
"source": Relationship.objects.get_for_model_source(model),
|
|
375
|
+
"destination": Relationship.objects.get_for_model_destination(model),
|
|
377
376
|
}
|
|
378
377
|
|
|
379
378
|
prefix = ""
|
nautobot/core/jobs/__init__.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import codecs
|
|
1
2
|
import contextlib
|
|
2
3
|
from io import BytesIO
|
|
3
4
|
|
|
@@ -282,7 +283,9 @@ class ImportObjects(Job):
|
|
|
282
283
|
if not csv_data and not csv_file:
|
|
283
284
|
raise RunJobTaskFailed("Either csv_data or csv_file must be provided")
|
|
284
285
|
if csv_file:
|
|
285
|
-
|
|
286
|
+
# data_encoding is utf-8 and file_encoding is utf-8-sig
|
|
287
|
+
# Bytes read from the original file are decoded according to file_encoding, and the result is encoded using data_encoding.
|
|
288
|
+
csv_bytes = codecs.EncodedFile(csv_file, "utf-8", "utf-8-sig")
|
|
286
289
|
else:
|
|
287
290
|
csv_bytes = BytesIO(csv_data.encode("utf-8"))
|
|
288
291
|
|
nautobot/core/settings.py
CHANGED
|
@@ -907,13 +907,24 @@ CELERY_TASK_TRACK_STARTED = True
|
|
|
907
907
|
# If enabled, a `task-sent` event will be sent for every task so tasks can be tracked before they're consumed by a worker.
|
|
908
908
|
CELERY_TASK_SEND_SENT_EVENT = True
|
|
909
909
|
|
|
910
|
+
# How many tasks a worker is allowed to reserve for its own consumption and execution.
|
|
911
|
+
# If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
|
|
912
|
+
# For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
|
|
913
|
+
# Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
|
|
914
|
+
# so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
|
|
915
|
+
# until the long-running task completes.
|
|
916
|
+
# https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
|
|
917
|
+
CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
|
|
918
|
+
|
|
910
919
|
# If enabled stdout and stderr of running jobs will be redirected to the task logger.
|
|
911
920
|
CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
|
|
912
921
|
|
|
913
|
-
# The log level of log messages generated by redirected job stdout and stderr.
|
|
922
|
+
# The log level of log messages generated by redirected job stdout and stderr.
|
|
923
|
+
# Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
|
|
914
924
|
CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
|
|
915
925
|
|
|
916
|
-
# Send task-related events so that tasks can be monitored using tools like flower.
|
|
926
|
+
# Send task-related events so that tasks can be monitored using tools like flower.
|
|
927
|
+
# Sets the default value for the workers -E argument.
|
|
917
928
|
CELERY_WORKER_SEND_TASK_EVENTS = True
|
|
918
929
|
|
|
919
930
|
# Default celery queue name that will be used by workers and tasks if no queue is specified
|
nautobot/core/settings.yaml
CHANGED
|
@@ -426,6 +426,20 @@ properties:
|
|
|
426
426
|
see_also:
|
|
427
427
|
"`CELERY_TASK_SOFT_TIME_LIMIT`": "#celery_task_soft_time_limit"
|
|
428
428
|
type: "integer"
|
|
429
|
+
CELERY_WORKER_PREFETCH_MULTIPLIER:
|
|
430
|
+
default: 4
|
|
431
|
+
description: "How many tasks a worker is allowed to reserve for its own consumption and execution."
|
|
432
|
+
details: >-
|
|
433
|
+
If set to `0` (not recommended) a single worker can reserve all tasks even if other workers are free.
|
|
434
|
+
For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
|
|
435
|
+
Conversely, for long-running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to `1`
|
|
436
|
+
so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
|
|
437
|
+
until the long-running task completes.
|
|
438
|
+
environment_variable: "NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER"
|
|
439
|
+
see_also:
|
|
440
|
+
"Celery documentation": "https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits"
|
|
441
|
+
type: "integer"
|
|
442
|
+
version_added: "2.2.9"
|
|
429
443
|
CELERY_WORKER_PROMETHEUS_PORTS:
|
|
430
444
|
default: []
|
|
431
445
|
description: "Ports for Prometheus metric HTTP server running on the celery worker(s)."
|
|
@@ -282,6 +282,15 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
|
|
|
282
282
|
# CELERY_TASK_SOFT_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_SOFT_TIME_LIMIT", str(5 * 60)))
|
|
283
283
|
# CELERY_TASK_TIME_LIMIT = int(os.getenv("NAUTOBOT_CELERY_TASK_TIME_LIMIT", str(10 * 60)))
|
|
284
284
|
|
|
285
|
+
# How many tasks a worker is allowed to reserve for its own consumption and execution.
|
|
286
|
+
# If set to zero (not recommended) a single worker can reserve all tasks even if other workers are free.
|
|
287
|
+
# For short running tasks (such as webhooks) you may want to set this to a larger number to increase throughput.
|
|
288
|
+
# Conversely, for long running tasks (such as SSoT or Golden-Config Jobs at scale) you may want to set this to 1
|
|
289
|
+
# so that a worker executing a long-running task will not prefetch other tasks, which would block their execution
|
|
290
|
+
# until the long-running task completes.
|
|
291
|
+
# https://docs.celeryq.dev/en/stable/userguide/optimizing.html#prefetch-limits
|
|
292
|
+
# CELERY_WORKER_PREFETCH_MULTIPLIER = int(os.getenv("NAUTOBOT_CELERY_WORKER_PREFETCH_MULTIPLIER", "4"))
|
|
293
|
+
|
|
285
294
|
# Ports for prometheus metric HTTP server running on the celery worker.
|
|
286
295
|
# Normally this should be set to a single port, unless you have multiple workers running on a single machine, i.e.
|
|
287
296
|
# sharing the same available ports. In that case you need to specify a range of ports greater than or equal to the
|
|
@@ -294,6 +303,12 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
|
|
|
294
303
|
# int(value) for value in os.getenv("NAUTOBOT_CELERY_WORKER_PROMETHEUS_PORTS").split(",")
|
|
295
304
|
# ]
|
|
296
305
|
|
|
306
|
+
# If enabled stdout and stderr of running jobs will be redirected to the task logger.
|
|
307
|
+
# CELERY_WORKER_REDIRECT_STDOUTS = is_truthy(os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS", "True"))
|
|
308
|
+
|
|
309
|
+
# The log level of log messages generated by redirected job stdout and stderr.
|
|
310
|
+
# Can be one of `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`.
|
|
311
|
+
# CELERY_WORKER_REDIRECT_STDOUTS_LEVEL = os.getenv("NAUTOBOT_CELERY_WORKER_REDIRECT_STDOUTS_LEVEL", "WARNING")
|
|
297
312
|
|
|
298
313
|
# Number of days to retain changelog entries. Set to 0 to retain changes indefinitely. Defaults to 90 if not set here.
|
|
299
314
|
#
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from django.core.files.base import ContentFile
|
|
4
5
|
import yaml
|
|
5
6
|
|
|
6
7
|
from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
|
|
7
|
-
from nautobot.dcim.models import DeviceType, Location, LocationType, Manufacturer
|
|
8
|
+
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
8
9
|
from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
|
|
9
|
-
from nautobot.extras.models import
|
|
10
|
+
from nautobot.extras.models import (
|
|
11
|
+
Contact,
|
|
12
|
+
ContactAssociation,
|
|
13
|
+
ExportTemplate,
|
|
14
|
+
FileProxy,
|
|
15
|
+
JobLogEntry,
|
|
16
|
+
Role,
|
|
17
|
+
Status,
|
|
18
|
+
)
|
|
19
|
+
from nautobot.ipam.models import Prefix
|
|
10
20
|
from nautobot.users.models import ObjectPermission
|
|
11
21
|
|
|
12
22
|
|
|
@@ -204,6 +214,76 @@ class ImportObjectsTestCase(TransactionTestCase):
|
|
|
204
214
|
)
|
|
205
215
|
self.assertEqual(4, Status.objects.filter(name__startswith="test_status").count())
|
|
206
216
|
|
|
217
|
+
def test_csv_import_with_utf_8_with_bom_encoding(self):
|
|
218
|
+
"""
|
|
219
|
+
A superuser running the job with a .csv file with utf_8 with bom encoding should successfully create all specified objects.
|
|
220
|
+
Test for bug fix https://github.com/nautobot/nautobot/issues/5812 and https://github.com/nautobot/nautobot/issues/5985
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
status = Status.objects.get(name="Active").pk
|
|
224
|
+
content = f"prefix,status\n192.168.1.1/32,{status}"
|
|
225
|
+
content = content.encode("utf-8-sig")
|
|
226
|
+
filename = "test.csv"
|
|
227
|
+
csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
|
|
228
|
+
job_result = create_job_result_and_run_job(
|
|
229
|
+
"nautobot.core.jobs",
|
|
230
|
+
"ImportObjects",
|
|
231
|
+
content_type=ContentType.objects.get_for_model(Prefix).pk,
|
|
232
|
+
csv_file=csv_file.id,
|
|
233
|
+
)
|
|
234
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
235
|
+
self.assertFalse(
|
|
236
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
|
|
237
|
+
)
|
|
238
|
+
self.assertFalse(
|
|
239
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
|
|
240
|
+
)
|
|
241
|
+
self.assertEqual(
|
|
242
|
+
1, Prefix.objects.filter(status=Status.objects.get(name="Active"), prefix="192.168.1.1/32").count()
|
|
243
|
+
)
|
|
244
|
+
mfr = Manufacturer.objects.create(name="Test Cisco Manufacturer")
|
|
245
|
+
device_type = DeviceType.objects.create(
|
|
246
|
+
manufacturer=mfr,
|
|
247
|
+
model="Cisco CSR1000v",
|
|
248
|
+
u_height=0,
|
|
249
|
+
)
|
|
250
|
+
location_type = LocationType.objects.create(name="Test Location Type")
|
|
251
|
+
location_type.content_types.set([ContentType.objects.get_for_model(Device)])
|
|
252
|
+
location = Location.objects.create(
|
|
253
|
+
name="Device Location",
|
|
254
|
+
location_type=location_type,
|
|
255
|
+
status=Status.objects.get_for_model(Location).first(),
|
|
256
|
+
)
|
|
257
|
+
role = Role.objects.create(name="Device Status")
|
|
258
|
+
role.content_types.set([ContentType.objects.get_for_model(Device)])
|
|
259
|
+
content = "\n".join(
|
|
260
|
+
[
|
|
261
|
+
"serial,asset_tag,device_type,location,status,name,role",
|
|
262
|
+
f"1021C4,CA211,{device_type.pk},{location.pk},{status},Test-AC-01,{role}",
|
|
263
|
+
f"1021C5,CA212,{device_type.pk},{location.pk},{status},Test-AC-02,{role}",
|
|
264
|
+
]
|
|
265
|
+
)
|
|
266
|
+
content = content.encode("utf-8-sig")
|
|
267
|
+
filename = "test.csv"
|
|
268
|
+
csv_file = FileProxy.objects.create(name=filename, file=ContentFile(content, name=filename))
|
|
269
|
+
job_result = create_job_result_and_run_job(
|
|
270
|
+
"nautobot.core.jobs",
|
|
271
|
+
"ImportObjects",
|
|
272
|
+
content_type=ContentType.objects.get_for_model(Device).pk,
|
|
273
|
+
csv_file=csv_file.id,
|
|
274
|
+
)
|
|
275
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
276
|
+
self.assertFalse(
|
|
277
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
|
|
278
|
+
)
|
|
279
|
+
self.assertFalse(
|
|
280
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
|
|
281
|
+
)
|
|
282
|
+
device_1 = Device.objects.get(name="Test-AC-01")
|
|
283
|
+
device_2 = Device.objects.get(name="Test-AC-02")
|
|
284
|
+
self.assertEqual(device_1.serial, "1021C4")
|
|
285
|
+
self.assertEqual(device_2.serial, "1021C5")
|
|
286
|
+
|
|
207
287
|
def test_csv_import_bad_row(self):
|
|
208
288
|
"""A row of incorrect data should fail validation for that object but import all others successfully if `roll_back_if_error` is False."""
|
|
209
289
|
csv_data = self.csv_data.split("\n")
|
|
@@ -20,15 +20,15 @@ class NautobotTemplateTagsNetutilsTest(TestCase):
|
|
|
20
20
|
i = 1
|
|
21
21
|
for param_name, param in signature.parameters.items():
|
|
22
22
|
template_string += f" {param_name}="
|
|
23
|
-
if param.annotation
|
|
23
|
+
if param.annotation is str:
|
|
24
24
|
template_string += f'"{i}"'
|
|
25
|
-
elif param.annotation
|
|
25
|
+
elif param.annotation is bool:
|
|
26
26
|
template_string += "True"
|
|
27
27
|
elif param.annotation in (int, float):
|
|
28
28
|
template_string += str(i)
|
|
29
29
|
elif param.annotation in (list, tuple):
|
|
30
30
|
template_string += "[]"
|
|
31
|
-
elif param.annotation
|
|
31
|
+
elif param.annotation is dict:
|
|
32
32
|
template_string += "{}"
|
|
33
33
|
else:
|
|
34
34
|
template_string += "None"
|
|
@@ -24,6 +24,7 @@ from nautobot.dcim.choices import (
|
|
|
24
24
|
PowerOutletFeedLegChoices,
|
|
25
25
|
PowerOutletTypeChoices,
|
|
26
26
|
PowerPortTypeChoices,
|
|
27
|
+
SubdeviceRoleChoices,
|
|
27
28
|
)
|
|
28
29
|
from nautobot.dcim.constants import (
|
|
29
30
|
NONCONNECTABLE_IFACE_TYPES,
|
|
@@ -1019,6 +1020,12 @@ class DeviceBay(ComponentModel):
|
|
|
1019
1020
|
"installed_device": f"Cannot install the specified device; device is already installed in {current_bay}"
|
|
1020
1021
|
}
|
|
1021
1022
|
)
|
|
1023
|
+
if self.installed_device.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_CHILD:
|
|
1024
|
+
raise ValidationError(
|
|
1025
|
+
{
|
|
1026
|
+
"installed_device": f'Cannot install device "{self.installed_device}"; device-type "{self.installed_device.device_type}" subdevice_role is not "child".'
|
|
1027
|
+
}
|
|
1028
|
+
)
|
|
1022
1029
|
|
|
1023
1030
|
@property
|
|
1024
1031
|
def parent(self):
|
nautobot/dcim/models/devices.py
CHANGED
|
@@ -1065,6 +1065,10 @@ class DeviceRedundancyGroup(PrimaryModel):
|
|
|
1065
1065
|
def devices_sorted(self):
|
|
1066
1066
|
return self.devices.order_by("device_redundancy_group_priority")
|
|
1067
1067
|
|
|
1068
|
+
@property
|
|
1069
|
+
def controllers_sorted(self):
|
|
1070
|
+
return self.controllers.order_by("name")
|
|
1071
|
+
|
|
1068
1072
|
def __str__(self):
|
|
1069
1073
|
return self.name
|
|
1070
1074
|
|
nautobot/dcim/tables/devices.py
CHANGED
|
@@ -938,17 +938,32 @@ class VirtualChassisTable(BaseTable):
|
|
|
938
938
|
class DeviceRedundancyGroupTable(BaseTable):
|
|
939
939
|
pk = ToggleColumn()
|
|
940
940
|
name = tables.Column(linkify=True)
|
|
941
|
-
device_count =
|
|
942
|
-
|
|
941
|
+
device_count = LinkedCountColumn(
|
|
942
|
+
viewname="dcim:device_list",
|
|
943
|
+
url_params={"device_redundancy_group": "pk"},
|
|
943
944
|
verbose_name="Devices",
|
|
944
945
|
)
|
|
946
|
+
controller_count = LinkedCountColumn(
|
|
947
|
+
viewname="dcim:controller_list",
|
|
948
|
+
url_params={"controller_device_redundancy_group": "pk"},
|
|
949
|
+
verbose_name="Controllers",
|
|
950
|
+
)
|
|
945
951
|
secrets_group = tables.Column(linkify=True)
|
|
946
952
|
tags = TagColumn(url_name="dcim:deviceredundancygroup_list")
|
|
947
953
|
|
|
948
954
|
class Meta(BaseTable.Meta):
|
|
949
955
|
model = DeviceRedundancyGroup
|
|
950
|
-
fields = (
|
|
951
|
-
|
|
956
|
+
fields = (
|
|
957
|
+
"pk",
|
|
958
|
+
"name",
|
|
959
|
+
"status",
|
|
960
|
+
"failover_strategy",
|
|
961
|
+
"controller_count",
|
|
962
|
+
"device_count",
|
|
963
|
+
"secrets_group",
|
|
964
|
+
"tags",
|
|
965
|
+
)
|
|
966
|
+
default_columns = ("pk", "name", "status", "failover_strategy", "controller_count", "device_count")
|
|
952
967
|
|
|
953
968
|
|
|
954
969
|
#
|
|
@@ -45,6 +45,12 @@
|
|
|
45
45
|
{% endblock content_right_page %}
|
|
46
46
|
|
|
47
47
|
{% block content_full_width_page %}
|
|
48
|
+
<div class="panel panel-default">
|
|
49
|
+
<div class="panel-heading">
|
|
50
|
+
<strong>Controllers</strong>
|
|
51
|
+
</div>
|
|
52
|
+
{% include 'responsive_table.html' with table=controllers_table %}
|
|
53
|
+
</div>
|
|
48
54
|
<div class="panel panel-default">
|
|
49
55
|
<div class="panel-heading">
|
|
50
56
|
<strong>Devices</strong>
|
|
@@ -1264,6 +1264,37 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1264
1264
|
self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
|
|
1265
1265
|
|
|
1266
1266
|
|
|
1267
|
+
class DeviceBayTestCase(ModelTestCases.BaseModelTestCase):
|
|
1268
|
+
model = DeviceBay
|
|
1269
|
+
|
|
1270
|
+
def setUp(self):
|
|
1271
|
+
self.devices = Device.objects.filter(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
|
|
1272
|
+
devicetype = DeviceType.objects.create(
|
|
1273
|
+
manufacturer=self.devices[0].device_type.manufacturer,
|
|
1274
|
+
model="TestDeviceType1",
|
|
1275
|
+
u_height=0,
|
|
1276
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
|
|
1277
|
+
)
|
|
1278
|
+
child_device = Device.objects.create(
|
|
1279
|
+
device_type=devicetype,
|
|
1280
|
+
role=self.devices[0].role,
|
|
1281
|
+
name="TestDevice1",
|
|
1282
|
+
status=self.devices[0].status,
|
|
1283
|
+
location=self.devices[0].location,
|
|
1284
|
+
)
|
|
1285
|
+
DeviceBay.objects.create(device=self.devices[0], name="Device Bay 1", installed_device=child_device)
|
|
1286
|
+
|
|
1287
|
+
def test_assigning_installed_device(self):
|
|
1288
|
+
server = Device.objects.exclude(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD).last()
|
|
1289
|
+
bay = DeviceBay(device=self.devices[1], name="Device Bay Err", installed_device=server)
|
|
1290
|
+
with self.assertRaises(ValidationError) as err:
|
|
1291
|
+
bay.validated_save()
|
|
1292
|
+
self.assertIn(
|
|
1293
|
+
f'Cannot install device "{server}"; device-type "{server.device_type}" subdevice_role is not "child".',
|
|
1294
|
+
str(err.exception),
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
|
|
1267
1298
|
class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
|
|
1268
1299
|
model = DeviceTypeToSoftwareImageFile
|
|
1269
1300
|
|
|
@@ -1789,6 +1789,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1789
1789
|
sorted(interface_ips),
|
|
1790
1790
|
)
|
|
1791
1791
|
|
|
1792
|
+
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
1793
|
+
assign_ip_form_data["pk"] = []
|
|
1794
|
+
assign_ip_request = {
|
|
1795
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
1796
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
1797
|
+
"data": post_data(assign_ip_form_data),
|
|
1798
|
+
}
|
|
1799
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
1800
|
+
self.assertHttpStatus(response, 200)
|
|
1801
|
+
self.assertIn(
|
|
1802
|
+
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1792
1805
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1793
1806
|
def test_device_rearports(self):
|
|
1794
1807
|
device = Device.objects.first()
|
nautobot/dcim/views.py
CHANGED
|
@@ -3029,8 +3029,11 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3029
3029
|
form_class = forms.DeviceRedundancyGroupForm
|
|
3030
3030
|
queryset = (
|
|
3031
3031
|
DeviceRedundancyGroup.objects.select_related("status")
|
|
3032
|
-
.prefetch_related("devices")
|
|
3033
|
-
.annotate(
|
|
3032
|
+
.prefetch_related("controllers", "devices")
|
|
3033
|
+
.annotate(
|
|
3034
|
+
device_count=count_related(Device, "device_redundancy_group"),
|
|
3035
|
+
controller_count=count_related(Controller, "controller_device_redundancy_group"),
|
|
3036
|
+
)
|
|
3034
3037
|
)
|
|
3035
3038
|
serializer_class = serializers.DeviceRedundancyGroupSerializer
|
|
3036
3039
|
table_class = tables.DeviceRedundancyGroupTable
|
|
@@ -3043,6 +3046,9 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3043
3046
|
devices_table = tables.DeviceTable(devices)
|
|
3044
3047
|
devices_table.columns.show("device_redundancy_group_priority")
|
|
3045
3048
|
context["devices_table"] = devices_table
|
|
3049
|
+
controllers = instance.controllers_sorted.restrict(request.user)
|
|
3050
|
+
controllers_table = tables.ControllerTable(controllers)
|
|
3051
|
+
context["controllers_table"] = controllers_table
|
|
3046
3052
|
return context
|
|
3047
3053
|
|
|
3048
3054
|
|
nautobot/extras/api/views.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
|
-
|
|
3
1
|
from django.conf import settings
|
|
4
2
|
from django.contrib.contenttypes.models import ContentType
|
|
5
3
|
from django.forms import ValidationError as FormsValidationError
|
|
@@ -449,59 +447,6 @@ class ImageAttachmentViewSet(ModelViewSet):
|
|
|
449
447
|
#
|
|
450
448
|
|
|
451
449
|
|
|
452
|
-
def _create_schedule(serializer, data, job_model, user, approval_required, task_queue=None):
|
|
453
|
-
"""
|
|
454
|
-
This is an internal function to create a scheduled job from API data.
|
|
455
|
-
It has to handle both once-offs (i.e. of type TYPE_FUTURE) and interval
|
|
456
|
-
jobs.
|
|
457
|
-
"""
|
|
458
|
-
type_ = serializer["interval"]
|
|
459
|
-
if type_ == JobExecutionType.TYPE_IMMEDIATELY:
|
|
460
|
-
time = timezone.now()
|
|
461
|
-
name = serializer.get("name") or f"{job_model.name} - {time}"
|
|
462
|
-
elif type_ == JobExecutionType.TYPE_CUSTOM:
|
|
463
|
-
time = serializer.get("start_time") # doing .get("key", "default") returns None instead of "default"
|
|
464
|
-
if time is None:
|
|
465
|
-
# "start_time" is checked against models.ScheduledJob.earliest_possible_time()
|
|
466
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
467
|
-
time = timezone.now() + timedelta(seconds=20)
|
|
468
|
-
name = serializer["name"]
|
|
469
|
-
else:
|
|
470
|
-
time = serializer["start_time"]
|
|
471
|
-
name = serializer["name"]
|
|
472
|
-
crontab = serializer.get("crontab", "")
|
|
473
|
-
|
|
474
|
-
celery_kwargs = {
|
|
475
|
-
"nautobot_job_profile": False,
|
|
476
|
-
"queue": task_queue,
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
# 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
|
|
480
|
-
#
|
|
481
|
-
# We pass in task and job_model here partly for forward/backward compatibility logic, and
|
|
482
|
-
# part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
|
|
483
|
-
# FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
|
|
484
|
-
# null) you still have a bit of context on the ScheduledJob as to what it was originally
|
|
485
|
-
# scheduled for.
|
|
486
|
-
scheduled_job = ScheduledJob(
|
|
487
|
-
name=name,
|
|
488
|
-
task=job_model.class_path,
|
|
489
|
-
job_model=job_model,
|
|
490
|
-
start_time=time,
|
|
491
|
-
description=f"Nautobot job {name} scheduled by {user} for {time}",
|
|
492
|
-
kwargs=data,
|
|
493
|
-
celery_kwargs=celery_kwargs,
|
|
494
|
-
interval=type_,
|
|
495
|
-
one_off=(type_ == JobExecutionType.TYPE_FUTURE),
|
|
496
|
-
user=user,
|
|
497
|
-
approval_required=approval_required,
|
|
498
|
-
crontab=crontab,
|
|
499
|
-
queue=task_queue,
|
|
500
|
-
)
|
|
501
|
-
scheduled_job.validated_save()
|
|
502
|
-
return scheduled_job
|
|
503
|
-
|
|
504
|
-
|
|
505
450
|
class JobViewSetBase(
|
|
506
451
|
NautobotAPIVersionMixin,
|
|
507
452
|
# note no CreateModelMixin
|
|
@@ -701,13 +646,16 @@ class JobViewSetBase(
|
|
|
701
646
|
|
|
702
647
|
# Try to create a ScheduledJob, or...
|
|
703
648
|
if schedule_data:
|
|
704
|
-
schedule =
|
|
705
|
-
schedule_data,
|
|
706
|
-
job_class.serialize_data(cleaned_data),
|
|
649
|
+
schedule = ScheduledJob.create_schedule(
|
|
707
650
|
job_model,
|
|
708
651
|
request.user,
|
|
709
|
-
|
|
652
|
+
name=schedule_data.get("name"),
|
|
653
|
+
start_time=schedule_data.get("start_time"),
|
|
654
|
+
interval=schedule_data.get("interval"),
|
|
655
|
+
crontab=schedule_data.get("crontab", ""),
|
|
656
|
+
approval_required=approval_required,
|
|
710
657
|
task_queue=input_serializer.validated_data.get("task_queue", None),
|
|
658
|
+
**job_class.serialize_data(cleaned_data),
|
|
711
659
|
)
|
|
712
660
|
else:
|
|
713
661
|
schedule = None
|
nautobot/extras/homepage.py
CHANGED
|
@@ -7,14 +7,24 @@ def get_job_results(request):
|
|
|
7
7
|
"""Callback function to collect job history for panel."""
|
|
8
8
|
return (
|
|
9
9
|
JobResult.objects.filter(status__in=JobResultStatusChoices.READY_STATES)
|
|
10
|
-
.
|
|
10
|
+
.restrict(request.user, "view")
|
|
11
|
+
.only("id", "name", "status", "date_done", "user")
|
|
11
12
|
.order_by("-date_done")[:10]
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def get_changelog(request):
|
|
16
17
|
"""Callback function to collect changelog for panel."""
|
|
17
|
-
return ObjectChange.objects.restrict(request.user, "view")
|
|
18
|
+
return ObjectChange.objects.restrict(request.user, "view").only(
|
|
19
|
+
"id",
|
|
20
|
+
"action",
|
|
21
|
+
"changed_object",
|
|
22
|
+
"changed_object_id",
|
|
23
|
+
"changed_object_type",
|
|
24
|
+
"object_repr",
|
|
25
|
+
"user_name",
|
|
26
|
+
"time",
|
|
27
|
+
)[:15]
|
|
18
28
|
|
|
19
29
|
|
|
20
30
|
layout = (
|
nautobot/extras/jobs.py
CHANGED
|
@@ -20,7 +20,7 @@ from django.conf import settings
|
|
|
20
20
|
from django.contrib.auth import get_user_model
|
|
21
21
|
from django.core.exceptions import ObjectDoesNotExist
|
|
22
22
|
from django.core.files.base import ContentFile
|
|
23
|
-
from django.core.files.uploadedfile import
|
|
23
|
+
from django.core.files.uploadedfile import UploadedFile
|
|
24
24
|
from django.core.validators import RegexValidator
|
|
25
25
|
from django.db.models import Model
|
|
26
26
|
from django.db.models.query import QuerySet
|
|
@@ -539,7 +539,7 @@ class BaseJob:
|
|
|
539
539
|
elif isinstance(value, Model):
|
|
540
540
|
return_data[field_name] = value.pk
|
|
541
541
|
# FileVar (Save each FileVar as a FileProxy)
|
|
542
|
-
elif isinstance(value,
|
|
542
|
+
elif isinstance(value, UploadedFile):
|
|
543
543
|
return_data[field_name] = BaseJob._save_file_to_proxy(value)
|
|
544
544
|
# IPAddressVar, IPAddressWithMaskVar, IPNetworkVar
|
|
545
545
|
elif isinstance(value, netaddr.ip.BaseIP):
|