nautobot 2.2.7__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 +4 -4
- nautobot/core/graphql/schema.py +9 -7
- 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 +36 -0
- nautobot/core/tests/test_graphql.py +51 -5
- 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 +14 -3
- nautobot/dcim/tables/devices.py +19 -4
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/tests/test_models.py +106 -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/models/relationships.py +12 -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/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_relationships.py +221 -1
- 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 +4 -4
- nautobot/project-static/docs/apps/index.html +4 -4
- nautobot/project-static/docs/apps/nautobot-apps.html +4 -4
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css → main.76a95c52.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css.map → main.76a95c52.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +6 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +4 -4
- nautobot/project-static/docs/development/apps/api/configuration-view.html +4 -4
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/global-search.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/graphql.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +4 -4
- nautobot/project-static/docs/development/apps/api/prometheus.html +4 -4
- nautobot/project-static/docs/development/apps/api/setup.html +4 -4
- nautobot/project-static/docs/development/apps/api/testing.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/base-template.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/urls.html +4 -4
- nautobot/project-static/docs/development/apps/index.html +4 -4
- nautobot/project-static/docs/development/apps/migration/code-updates.html +4 -4
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +4 -4
- nautobot/project-static/docs/development/apps/migration/from-v1.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +4 -4
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +4 -4
- nautobot/project-static/docs/development/core/application-registry.html +4 -4
- nautobot/project-static/docs/development/core/best-practices.html +4 -4
- nautobot/project-static/docs/development/core/bootstrap-ui.html +4 -4
- nautobot/project-static/docs/development/core/caching.html +4 -4
- nautobot/project-static/docs/development/core/controllers.html +4 -4
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +4 -4
- nautobot/project-static/docs/development/core/generic-views.html +4 -4
- nautobot/project-static/docs/development/core/getting-started.html +4 -4
- nautobot/project-static/docs/development/core/homepage.html +4 -4
- nautobot/project-static/docs/development/core/index.html +15 -4
- nautobot/project-static/docs/development/core/model-checklist.html +4 -4
- nautobot/project-static/docs/development/core/model-features.html +4 -4
- nautobot/project-static/docs/development/core/natural-keys.html +4 -4
- nautobot/project-static/docs/development/core/navigation-menu.html +4 -4
- nautobot/project-static/docs/development/core/release-checklist.html +4 -4
- nautobot/project-static/docs/development/core/role-internals.html +4 -4
- nautobot/project-static/docs/development/core/settings.html +4 -4
- nautobot/project-static/docs/development/core/style-guide.html +4 -4
- nautobot/project-static/docs/development/core/templates.html +4 -4
- nautobot/project-static/docs/development/core/testing.html +4 -4
- nautobot/project-static/docs/development/core/user-preferences.html +4 -4
- nautobot/project-static/docs/development/index.html +4 -4
- nautobot/project-static/docs/development/jobs/index.html +379 -365
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +4 -4
- nautobot/project-static/docs/index.html +8228 -13
- nautobot/project-static/docs/overview/application_stack.html +4 -4
- nautobot/project-static/docs/overview/design_philosophy.html +6 -6
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.0.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.1.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.2.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.3.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.4.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.5.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.6.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.0.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.1.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.2.html +419 -136
- nautobot/project-static/docs/requirements.txt +2 -1
- 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 +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +36 -4
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +8 -4
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +8 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -5
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +4 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +4 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +4 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +62 -10
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +4 -4
- nautobot/project-static/docs/user-guide/index.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +4 -4
- nautobot/virtualization/tables.py +2 -5
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/METADATA +3 -3
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/RECORD +306 -305
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
- {nautobot-2.2.7.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
|
|
@@ -55,17 +55,17 @@ def generate_null_choices_resolver(name, resolver_name):
|
|
|
55
55
|
|
|
56
56
|
def generate_filter_resolver(schema_type, resolver_name, field_name):
|
|
57
57
|
"""
|
|
58
|
-
Generate function to resolve
|
|
58
|
+
Generate function to resolve filtering of ManyToOne and ManyToMany related objects.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
61
|
schema_type (DjangoObjectType): DjangoObjectType for a given model
|
|
62
62
|
resolver_name (str): name of the resolver
|
|
63
|
-
field_name (str): name of
|
|
63
|
+
field_name (str): name of ManyToOneField, ManyToManyRel, or ManyToOneRel field to filter
|
|
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
|
@@ -6,7 +6,8 @@ import logging
|
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from django.contrib.contenttypes.models import ContentType
|
|
8
8
|
from django.core.validators import ValidationError
|
|
9
|
-
from django.db.models
|
|
9
|
+
from django.db.models import ManyToManyField
|
|
10
|
+
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel
|
|
10
11
|
import graphene
|
|
11
12
|
from graphene.types import generic
|
|
12
13
|
|
|
@@ -198,9 +199,11 @@ def extend_schema_type_filter(schema_type, model):
|
|
|
198
199
|
(DjangoObjectType): The extended schema_type object
|
|
199
200
|
"""
|
|
200
201
|
for field in model._meta.get_fields():
|
|
201
|
-
# Check attribute is a ManyToOne field
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
# Check whether attribute is a ManyToOne or ManyToMany field
|
|
203
|
+
if not isinstance(field, (ManyToManyField, ManyToManyRel, ManyToOneRel)):
|
|
204
|
+
continue
|
|
205
|
+
# OneToOneRel is a subclass of ManyToOneRel, but we don't want to treat it as a list
|
|
206
|
+
if isinstance(field, OneToOneRel):
|
|
204
207
|
continue
|
|
205
208
|
child_schema_type = registry["graphql_types"].get(field.related_model._meta.label_lower)
|
|
206
209
|
if child_schema_type:
|
|
@@ -367,10 +370,9 @@ def extend_schema_type_relationships(schema_type, model):
|
|
|
367
370
|
"""Extend the schema type with attributes and resolvers corresponding
|
|
368
371
|
to the relationships associated with this model."""
|
|
369
372
|
|
|
370
|
-
ct = ContentType.objects.get_for_model(model)
|
|
371
373
|
relationships_by_side = {
|
|
372
|
-
"source": Relationship.objects.
|
|
373
|
-
"destination": Relationship.objects.
|
|
374
|
+
"source": Relationship.objects.get_for_model_source(model),
|
|
375
|
+
"destination": Relationship.objects.get_for_model_destination(model),
|
|
374
376
|
}
|
|
375
377
|
|
|
376
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
|
#
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from django.urls import reverse
|
|
2
|
+
|
|
3
|
+
from nautobot.core.testing.integration import SeleniumTestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StaticMediaFailureTestCase(SeleniumTestCase):
|
|
7
|
+
"""Integration test to make sure no static media failures are encountered."""
|
|
8
|
+
|
|
9
|
+
def setUp(self):
|
|
10
|
+
super().setUp()
|
|
11
|
+
self.user.is_superuser = True
|
|
12
|
+
self.user.is_staff = True
|
|
13
|
+
self.user.save()
|
|
14
|
+
self.login(self.user.username, self.password)
|
|
15
|
+
|
|
16
|
+
def tearDown(self):
|
|
17
|
+
self.logout()
|
|
18
|
+
super().tearDown()
|
|
19
|
+
|
|
20
|
+
def test_for_static_media_failure(self):
|
|
21
|
+
test_urls = [
|
|
22
|
+
reverse("home"),
|
|
23
|
+
reverse("api-root"),
|
|
24
|
+
reverse("graphql"),
|
|
25
|
+
reverse("api_docs"),
|
|
26
|
+
"/admin/",
|
|
27
|
+
"/static/docs/index.html",
|
|
28
|
+
]
|
|
29
|
+
for url in test_urls:
|
|
30
|
+
with self.subTest(test_url=url):
|
|
31
|
+
self.browser.visit(self.live_server_url + url)
|
|
32
|
+
# Wait for body element to appear
|
|
33
|
+
self.assertTrue(self.browser.is_element_present_by_tag("body", wait_time=10), "Page failed to load")
|
|
34
|
+
# Ensure we weren't redirected to another page
|
|
35
|
+
self.assertEqual(self.browser.url, self.live_server_url + url)
|
|
36
|
+
self.assertTrue(self.browser.is_text_not_present("Static Media Failure"))
|
|
@@ -729,8 +729,8 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
729
729
|
cls.location_type = LocationType.objects.get(name="Campus")
|
|
730
730
|
cls.location1 = Location.objects.filter(location_type=cls.location_type).first()
|
|
731
731
|
cls.location2 = Location.objects.filter(location_type=cls.location_type).last()
|
|
732
|
-
cls.location1.name = "Location-1"
|
|
733
|
-
cls.location2.name = "Location-2"
|
|
732
|
+
cls.location1.name = "Campus Location-1"
|
|
733
|
+
cls.location2.name = "Campus Location-2"
|
|
734
734
|
cls.location1.status = cls.location_statuses[0]
|
|
735
735
|
cls.location2.status = cls.location_statuses[1]
|
|
736
736
|
cls.location1.validated_save()
|
|
@@ -930,6 +930,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
930
930
|
cls.prefix1 = Prefix.objects.create(
|
|
931
931
|
prefix="10.0.1.0/24", namespace=cls.namespace, status=cls.prefix_statuses[0]
|
|
932
932
|
)
|
|
933
|
+
cls.prefix1.locations.add(cls.location1, cls.location2)
|
|
933
934
|
cls.ipaddr1 = IPAddress.objects.create(
|
|
934
935
|
address="10.0.1.1/24", namespace=cls.namespace, status=cls.ip_statuses[0]
|
|
935
936
|
)
|
|
@@ -968,6 +969,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
968
969
|
cls.prefix2 = Prefix.objects.create(
|
|
969
970
|
prefix="10.0.2.0/24", namespace=cls.namespace, status=cls.prefix_statuses[1]
|
|
970
971
|
)
|
|
972
|
+
cls.prefix2.locations.add(cls.location1, cls.location2)
|
|
971
973
|
cls.ipaddr2 = IPAddress.objects.create(
|
|
972
974
|
address="10.0.2.1/30", namespace=cls.namespace, status=cls.ip_statuses[1]
|
|
973
975
|
)
|
|
@@ -1507,9 +1509,9 @@ query {
|
|
|
1507
1509
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1508
1510
|
def test_query_locations_filter(self):
|
|
1509
1511
|
filters = (
|
|
1510
|
-
('name: "Location-1"', 1),
|
|
1511
|
-
('name: ["Location-1"]', 1),
|
|
1512
|
-
('name: ["Location-1", "Location-2"]', 2),
|
|
1512
|
+
('name: "Campus Location-1"', 1),
|
|
1513
|
+
('name: ["Campus Location-1"]', 1),
|
|
1514
|
+
('name: ["Campus Location-1", "Campus Location-2"]', 2),
|
|
1513
1515
|
('name__ic: "Location"', Location.objects.filter(name__icontains="Location").count()),
|
|
1514
1516
|
('name__ic: ["Location"]', Location.objects.filter(name__icontains="Location").count()),
|
|
1515
1517
|
('name__nic: "Location"', Location.objects.exclude(name__icontains="Location").count()),
|
|
@@ -1541,6 +1543,50 @@ query {
|
|
|
1541
1543
|
self.assertIsNone(result.errors)
|
|
1542
1544
|
self.assertEqual(len(result.data["locations"]), nbr_expected_results)
|
|
1543
1545
|
|
|
1546
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1547
|
+
def test_query_prefixes_nested_m2m_filter(self):
|
|
1548
|
+
"""
|
|
1549
|
+
Test functionality added to address https://github.com/nautobot/nautobot/issues/5906.
|
|
1550
|
+
|
|
1551
|
+
Prefix.locations is a ManyToManyField, which was not filterable in our GraphQL schema before this fix.
|
|
1552
|
+
"""
|
|
1553
|
+
query = 'query { prefixes (prefix_length__gte:16) { prefix locations (location_type:["Campus"]) { name } } }'
|
|
1554
|
+
result = self.execute_query(query)
|
|
1555
|
+
self.assertIsNone(result.errors)
|
|
1556
|
+
found_valid_location = False
|
|
1557
|
+
found_invalid_location = False
|
|
1558
|
+
for prefix_data in result.data["prefixes"]:
|
|
1559
|
+
for location_data in prefix_data["locations"]:
|
|
1560
|
+
if location_data["name"].startswith("Campus"):
|
|
1561
|
+
found_valid_location = True
|
|
1562
|
+
else:
|
|
1563
|
+
print(f"Found unexpected unfiltered location {location_data['name']} under {prefix_data['prefix']}")
|
|
1564
|
+
found_invalid_location = True
|
|
1565
|
+
self.assertTrue(found_valid_location)
|
|
1566
|
+
self.assertFalse(found_invalid_location)
|
|
1567
|
+
|
|
1568
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1569
|
+
def test_query_locations_nested_reverse_m2m_filter(self):
|
|
1570
|
+
"""
|
|
1571
|
+
Test functionality added to address https://github.com/nautobot/nautobot/issues/5906.
|
|
1572
|
+
|
|
1573
|
+
Location.prefixes is a (reverse) ManyToManyRel, which was not filterable in our GraphQL schema before this fix.
|
|
1574
|
+
"""
|
|
1575
|
+
query = "query { locations { name prefixes (prefix_length:24) { prefix } } }"
|
|
1576
|
+
result = self.execute_query(query)
|
|
1577
|
+
self.assertIsNone(result.errors)
|
|
1578
|
+
found_valid_prefix = False
|
|
1579
|
+
found_invalid_prefix = False
|
|
1580
|
+
for location_data in result.data["locations"]:
|
|
1581
|
+
for prefix_data in location_data["prefixes"]:
|
|
1582
|
+
if prefix_data["prefix"].endswith("/24"):
|
|
1583
|
+
found_valid_prefix = True
|
|
1584
|
+
else:
|
|
1585
|
+
print(f"Found unexpected unfiltered prefix {prefix_data['prefix']} under {location_data['name']}")
|
|
1586
|
+
found_invalid_prefix = True
|
|
1587
|
+
self.assertTrue(found_valid_prefix)
|
|
1588
|
+
self.assertFalse(found_invalid_prefix)
|
|
1589
|
+
|
|
1544
1590
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1545
1591
|
def test_query_devices_filter(self):
|
|
1546
1592
|
filterset_class = DeviceFilterSet
|
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
|
@@ -832,9 +832,16 @@ class Device(PrimaryModel, ConfigContextModel):
|
|
|
832
832
|
# Update Location and Rack assignment for any child Devices
|
|
833
833
|
devices = Device.objects.filter(parent_bay__device=self)
|
|
834
834
|
for device in devices:
|
|
835
|
-
|
|
836
|
-
device.
|
|
837
|
-
|
|
835
|
+
save_child_device = False
|
|
836
|
+
if device.location != self.location:
|
|
837
|
+
device.location = self.location
|
|
838
|
+
save_child_device = True
|
|
839
|
+
if device.rack != self.rack:
|
|
840
|
+
device.rack = self.rack
|
|
841
|
+
save_child_device = True
|
|
842
|
+
|
|
843
|
+
if save_child_device:
|
|
844
|
+
device.save()
|
|
838
845
|
|
|
839
846
|
def create_components(self):
|
|
840
847
|
"""Create device components from the device type definition."""
|
|
@@ -1058,6 +1065,10 @@ class DeviceRedundancyGroup(PrimaryModel):
|
|
|
1058
1065
|
def devices_sorted(self):
|
|
1059
1066
|
return self.devices.order_by("device_redundancy_group_priority")
|
|
1060
1067
|
|
|
1068
|
+
@property
|
|
1069
|
+
def controllers_sorted(self):
|
|
1070
|
+
return self.controllers.order_by("name")
|
|
1071
|
+
|
|
1061
1072
|
def __str__(self):
|
|
1062
1073
|
return self.name
|
|
1063
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>
|