nautobot 2.3.0b1__py3-none-any.whl → 2.3.1__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/cloud/factory.py +2 -0
- nautobot/cloud/filters.py +3 -0
- nautobot/cloud/forms.py +8 -2
- nautobot/cloud/migrations/0001_initial.py +1 -1
- nautobot/cloud/models.py +1 -2
- nautobot/cloud/tables.py +1 -17
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +11 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +4 -0
- nautobot/cloud/tests/test_filters.py +12 -0
- nautobot/cloud/tests/test_views.py +17 -0
- nautobot/cloud/views.py +1 -1
- nautobot/core/celery/__init__.py +5 -2
- nautobot/core/filters.py +15 -1
- nautobot/core/forms/forms.py +10 -2
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +6 -14
- nautobot/core/jobs/__init__.py +4 -1
- nautobot/core/management/commands/generate_test_data.py +2 -2
- nautobot/core/models/__init__.py +2 -2
- nautobot/core/settings.py +13 -2
- nautobot/core/settings.yaml +16 -2
- nautobot/core/tables.py +3 -0
- nautobot/core/templates/generic/object_retrieve.html +6 -6
- nautobot/core/templates/inc/computed_fields/panel_data.html +36 -24
- nautobot/core/templates/inc/object_details_advanced_panel.html +1 -1
- nautobot/core/templates/nautobot_config.py.j2 +15 -0
- nautobot/core/testing/filters.py +12 -1
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/test_jobs.py +74 -1
- nautobot/core/views/__init__.py +1 -1
- nautobot/core/views/generic.py +1 -1
- nautobot/core/views/mixins.py +1 -1
- nautobot/core/views/utils.py +8 -6
- nautobot/dcim/factory.py +4 -1
- nautobot/dcim/filters/__init__.py +4 -0
- nautobot/dcim/forms.py +24 -0
- nautobot/dcim/migrations/0061_module_models.py +1 -0
- nautobot/dcim/models/device_components.py +7 -0
- nautobot/dcim/models/devices.py +18 -19
- nautobot/dcim/models/racks.py +0 -1
- nautobot/dcim/tables/devices.py +17 -3
- nautobot/dcim/tables/devicetypes.py +1 -1
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device.html +3 -3
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +17 -0
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +3 -3
- nautobot/dcim/tests/test_api.py +2 -0
- nautobot/dcim/tests/test_filters.py +14 -7
- nautobot/dcim/tests/test_forms.py +54 -0
- nautobot/dcim/tests/test_models.py +40 -1
- nautobot/dcim/tests/test_views.py +45 -2
- nautobot/dcim/views.py +4 -1
- nautobot/extras/api/serializers.py +0 -1
- nautobot/extras/api/views.py +7 -59
- nautobot/extras/factory.py +50 -12
- nautobot/extras/filters/__init__.py +4 -1
- nautobot/extras/forms/base.py +10 -4
- nautobot/extras/forms/forms.py +1 -0
- nautobot/extras/homepage.py +12 -2
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/migrations/0111_metadata.py +4 -4
- nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
- nautobot/extras/models/customfields.py +54 -0
- nautobot/extras/models/jobs.py +83 -0
- nautobot/extras/models/metadata.py +18 -18
- nautobot/extras/models/models.py +2 -0
- nautobot/extras/signals.py +14 -1
- nautobot/extras/tables.py +43 -14
- nautobot/extras/templates/extras/computedfield.html +4 -0
- nautobot/extras/templates/extras/job_detail.html +11 -0
- nautobot/extras/tests/test_api.py +16 -9
- nautobot/extras/tests/test_jobs.py +2 -2
- nautobot/extras/tests/test_models.py +20 -18
- nautobot/extras/tests/test_views.py +23 -3
- nautobot/extras/utils.py +35 -6
- nautobot/extras/views.py +28 -51
- nautobot/ipam/filters.py +1 -1
- nautobot/ipam/forms.py +1 -1
- nautobot/ipam/models.py +9 -20
- nautobot/ipam/querysets.py +26 -0
- nautobot/ipam/tables.py +4 -0
- nautobot/ipam/tests/test_models.py +89 -2
- nautobot/ipam/views.py +10 -15
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +18 -18
- nautobot/project-static/docs/apps/index.html +18 -18
- nautobot/project-static/docs/apps/nautobot-apps.html +18 -18
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +66 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +66 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +34 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +82 -63
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +75 -111
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +34 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +161 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +18 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +21 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +34 -18
- nautobot/project-static/docs/development/apps/api/configuration-view.html +18 -18
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +18 -18
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +18 -18
- nautobot/project-static/docs/development/apps/api/models/global-search.html +18 -18
- nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -18
- nautobot/project-static/docs/development/apps/api/models/index.html +33 -22
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +18 -18
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +18 -18
- nautobot/project-static/docs/development/apps/api/prometheus.html +18 -18
- nautobot/project-static/docs/development/apps/api/setup.html +18 -18
- nautobot/project-static/docs/development/apps/api/testing.html +18 -18
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +18 -18
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +18 -18
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +18 -18
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +18 -18
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/base-template.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/index.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/notes.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +18 -18
- nautobot/project-static/docs/development/apps/api/views/urls.html +18 -18
- nautobot/project-static/docs/development/apps/index.html +18 -18
- nautobot/project-static/docs/development/apps/migration/code-updates.html +18 -18
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +18 -18
- nautobot/project-static/docs/development/apps/migration/from-v1.html +18 -18
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +18 -18
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +18 -18
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +18 -18
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +18 -18
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +18 -18
- nautobot/project-static/docs/development/core/application-registry.html +18 -18
- nautobot/project-static/docs/development/core/best-practices.html +18 -18
- nautobot/project-static/docs/development/core/bootstrap-ui.html +18 -18
- nautobot/project-static/docs/development/core/caching.html +18 -18
- nautobot/project-static/docs/development/core/controllers.html +18 -18
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +18 -18
- nautobot/project-static/docs/development/core/generic-views.html +18 -18
- nautobot/project-static/docs/development/core/getting-started.html +18 -18
- nautobot/project-static/docs/development/core/homepage.html +18 -18
- nautobot/project-static/docs/development/core/index.html +29 -18
- nautobot/project-static/docs/development/core/model-checklist.html +26 -20
- nautobot/project-static/docs/development/core/model-features.html +18 -18
- nautobot/project-static/docs/development/core/natural-keys.html +18 -18
- nautobot/project-static/docs/development/core/navigation-menu.html +18 -18
- nautobot/project-static/docs/development/core/release-checklist.html +18 -18
- nautobot/project-static/docs/development/core/role-internals.html +18 -18
- nautobot/project-static/docs/development/core/settings.html +18 -18
- nautobot/project-static/docs/development/core/style-guide.html +19 -19
- nautobot/project-static/docs/development/core/templates.html +18 -18
- nautobot/project-static/docs/development/core/testing.html +18 -18
- nautobot/project-static/docs/development/core/user-preferences.html +18 -18
- nautobot/project-static/docs/development/index.html +18 -18
- nautobot/project-static/docs/development/jobs/index.html +393 -379
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +18 -18
- nautobot/project-static/docs/index.html +9032 -13
- nautobot/project-static/docs/models/extras/metadatachoice.html +3 -3
- nautobot/project-static/docs/models/extras/metadatatype.html +3 -3
- nautobot/project-static/docs/models/extras/objectmetadata.html +3 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +18 -18
- nautobot/project-static/docs/overview/design_philosophy.html +20 -20
- nautobot/project-static/docs/overview/index.html +13 -9032
- nautobot/project-static/docs/release-notes/index.html +252 -19
- nautobot/project-static/docs/release-notes/version-1.0.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.1.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.2.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.3.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.4.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.5.html +18 -18
- nautobot/project-static/docs/release-notes/version-1.6.html +18 -18
- nautobot/project-static/docs/release-notes/version-2.0.html +18 -18
- nautobot/project-static/docs/release-notes/version-2.1.html +18 -18
- nautobot/project-static/docs/release-notes/version-2.2.html +248 -111
- nautobot/project-static/docs/release-notes/version-2.3.html +644 -90
- nautobot/project-static/docs/requirements.txt +3 -3
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +278 -278
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +18 -18
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +18 -18
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +18 -18
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +18 -18
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +52 -20
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +18 -18
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +22 -18
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +18 -18
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +18 -18
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +18 -18
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +18 -18
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +69 -82
- nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -24
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +60 -52
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +80 -87
- nautobot/project-static/docs/user-guide/administration/installation/services.html +37 -44
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +18 -18
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +18 -18
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +18 -18
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +18 -18
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +18 -18
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +76 -24
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +18 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +19 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +19 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +62 -18
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +18 -18
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +18 -18
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +18 -18
- nautobot/project-static/docs/user-guide/index.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +21 -21
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +36 -36
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +33 -33
- nautobot/project-static/docs/user-guide/platform-functionality/{metadata.html → objectmetadata.html} +197 -84
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +21 -21
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +18 -18
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +18 -18
- nautobot/tenancy/templates/tenancy/tenant.html +4 -4
- nautobot/virtualization/models.py +0 -2
- nautobot/virtualization/tables.py +2 -5
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/METADATA +3 -3
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/RECORD +378 -377
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/NOTICE +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/WHEEL +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -656,9 +656,15 @@ def common_test_data(cls):
|
|
|
656
656
|
device_redundancy_groups = iter(DeviceRedundancyGroup.objects.all())
|
|
657
657
|
|
|
658
658
|
module_types = (
|
|
659
|
-
ModuleType.objects.create(
|
|
660
|
-
|
|
661
|
-
|
|
659
|
+
ModuleType.objects.create(
|
|
660
|
+
manufacturer=cls.manufacturers[0], model="Filter Test Module Type 1", comments="Module Type 1"
|
|
661
|
+
),
|
|
662
|
+
ModuleType.objects.create(
|
|
663
|
+
manufacturer=cls.manufacturers[1], model="Filter Test Module Type 2", comments="Module Type 2"
|
|
664
|
+
),
|
|
665
|
+
ModuleType.objects.create(
|
|
666
|
+
manufacturer=cls.manufacturers[2], model="Filter Test Module Type 3", comments="Module Type 3"
|
|
667
|
+
),
|
|
662
668
|
)
|
|
663
669
|
|
|
664
670
|
# Create 3 of each component template on the first two module types
|
|
@@ -898,7 +904,7 @@ class ModularDeviceComponentTestMixin(DeviceComponentTestMixin):
|
|
|
898
904
|
)
|
|
899
905
|
parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
|
|
900
906
|
module_type = ModuleType.objects.create(
|
|
901
|
-
manufacturer=manufacturer, model=f"Test Device Filter for {model} Module Type"
|
|
907
|
+
manufacturer=manufacturer, model=f"Test Device Filter for {model} Module Type", comments="Module Type test"
|
|
902
908
|
)
|
|
903
909
|
module = Module.objects.create(
|
|
904
910
|
module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
|
|
@@ -2526,7 +2532,7 @@ class InterfaceTestCase(PathEndpointModelTestMixin, ModularDeviceComponentTestMi
|
|
|
2526
2532
|
name="Parent module bay", position="1", parent_device=device_vc_master
|
|
2527
2533
|
)
|
|
2528
2534
|
module_type = ModuleType.objects.create(
|
|
2529
|
-
manufacturer=manufacturer, model="Test Device Filter for Interface Module Type"
|
|
2535
|
+
manufacturer=manufacturer, model="Test Device Filter for Interface Module Type", comments="Module Type test"
|
|
2530
2536
|
)
|
|
2531
2537
|
module = Module.objects.create(
|
|
2532
2538
|
module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
|
|
@@ -2772,7 +2778,7 @@ class FrontPortTestCase(ModularDeviceComponentTestMixin, FilterTestCases.FilterT
|
|
|
2772
2778
|
)
|
|
2773
2779
|
parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
|
|
2774
2780
|
module_type = ModuleType.objects.create(
|
|
2775
|
-
manufacturer=manufacturer, model="Test Device Filter for FrontPort Module Type"
|
|
2781
|
+
manufacturer=manufacturer, model="Test Device Filter for FrontPort Module Type", comments="Module Type test"
|
|
2776
2782
|
)
|
|
2777
2783
|
module = Module.objects.create(
|
|
2778
2784
|
module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
|
|
@@ -3357,7 +3363,7 @@ class CableTestCase(FilterTestCases.FilterTestCase):
|
|
|
3357
3363
|
)
|
|
3358
3364
|
parent_module_bay = ModuleBay.objects.create(name="Parent module bay", position="1", parent_device=device)
|
|
3359
3365
|
module_type = ModuleType.objects.create(
|
|
3360
|
-
manufacturer=manufacturer, model="Test Device Filter for Cable Module Type"
|
|
3366
|
+
manufacturer=manufacturer, model="Test Device Filter for Cable Module Type", comments="Module Type test"
|
|
3361
3367
|
)
|
|
3362
3368
|
module = Module.objects.create(
|
|
3363
3369
|
module_type=module_type, parent_module_bay=parent_module_bay, status=self.module_statuses[0]
|
|
@@ -4012,6 +4018,7 @@ class ModuleTypeTestCase(FilterTestCases.FilterTestCase):
|
|
|
4012
4018
|
queryset = ModuleType.objects.all()
|
|
4013
4019
|
filterset = ModuleTypeFilterSet
|
|
4014
4020
|
generic_filter_tests = [
|
|
4021
|
+
("comments",),
|
|
4015
4022
|
("manufacturer", "manufacturer__id"),
|
|
4016
4023
|
("manufacturer", "manufacturer__name"),
|
|
4017
4024
|
("model",),
|
|
@@ -12,6 +12,8 @@ from nautobot.dcim.models import (
|
|
|
12
12
|
Manufacturer,
|
|
13
13
|
Platform,
|
|
14
14
|
Rack,
|
|
15
|
+
SoftwareImageFile,
|
|
16
|
+
SoftwareVersion,
|
|
15
17
|
)
|
|
16
18
|
from nautobot.extras.models import Role, SecretsGroup, Status
|
|
17
19
|
from nautobot.ipam.models import VLAN
|
|
@@ -43,6 +45,15 @@ class DeviceTestCase(FormTestCases.BaseFormTestCase):
|
|
|
43
45
|
cls.manufacturer = cls.device_type.manufacturer
|
|
44
46
|
cls.platform = Platform.objects.filter(manufacturer=cls.device_type.manufacturer).first()
|
|
45
47
|
cls.device_role = Role.objects.get_for_model(Device).first()
|
|
48
|
+
cls.software_version_contains_no_valid_image_for_device_type = SoftwareVersion.objects.create(
|
|
49
|
+
platform=cls.platform,
|
|
50
|
+
version="New version 1.0.0",
|
|
51
|
+
status=Status.objects.get_for_model(SoftwareVersion).first(),
|
|
52
|
+
)
|
|
53
|
+
cls.software_version = SoftwareVersion.objects.first()
|
|
54
|
+
cls.software_image_files = SoftwareImageFile.objects.exclude(software_version=cls.software_version).exclude(
|
|
55
|
+
default_image=True
|
|
56
|
+
)
|
|
46
57
|
|
|
47
58
|
Device.objects.create(
|
|
48
59
|
name="Device 1",
|
|
@@ -134,6 +145,49 @@ class DeviceTestCase(FormTestCases.BaseFormTestCase):
|
|
|
134
145
|
self.assertFalse(form.is_valid())
|
|
135
146
|
self.assertIn("face", form.errors)
|
|
136
147
|
|
|
148
|
+
def test_no_software_image_file_specified_is_valid(self):
|
|
149
|
+
form = DeviceForm(
|
|
150
|
+
data={
|
|
151
|
+
"name": "New Device",
|
|
152
|
+
"role": self.device_role.pk,
|
|
153
|
+
"tenant": None,
|
|
154
|
+
"manufacturer": self.manufacturer.pk,
|
|
155
|
+
"device_type": self.device_type.pk,
|
|
156
|
+
"location": self.location.pk,
|
|
157
|
+
"rack": None,
|
|
158
|
+
"face": None,
|
|
159
|
+
"position": None,
|
|
160
|
+
"platform": self.platform.pk,
|
|
161
|
+
"status": self.device_status.pk,
|
|
162
|
+
"secrets_group": SecretsGroup.objects.first().pk,
|
|
163
|
+
"software_version": self.software_version_contains_no_valid_image_for_device_type.pk,
|
|
164
|
+
"software_image_files": [],
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
self.assertTrue(form.is_valid())
|
|
168
|
+
|
|
169
|
+
def test_invalid_software_image_file_specified(self):
|
|
170
|
+
form = DeviceForm(
|
|
171
|
+
data={
|
|
172
|
+
"name": "New Device",
|
|
173
|
+
"role": self.device_role.pk,
|
|
174
|
+
"tenant": None,
|
|
175
|
+
"manufacturer": self.manufacturer.pk,
|
|
176
|
+
"device_type": self.device_type.pk,
|
|
177
|
+
"location": self.location.pk,
|
|
178
|
+
"rack": None,
|
|
179
|
+
"face": None,
|
|
180
|
+
"position": None,
|
|
181
|
+
"platform": self.platform.pk,
|
|
182
|
+
"status": self.device_status.pk,
|
|
183
|
+
"secrets_group": SecretsGroup.objects.first().pk,
|
|
184
|
+
"software_version": self.software_version.pk,
|
|
185
|
+
"software_image_files": list(self.software_image_files.values_list("pk", flat=True)),
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
self.assertFalse(form.is_valid())
|
|
189
|
+
self.assertIn("software_image_files", form.errors)
|
|
190
|
+
|
|
137
191
|
def test_non_racked_device_with_position(self):
|
|
138
192
|
form = DeviceForm(
|
|
139
193
|
data={
|
|
@@ -1723,11 +1723,19 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1723
1723
|
software_version.software_image_files.all().update(default_image=False)
|
|
1724
1724
|
self.device_type.software_image_files.set([])
|
|
1725
1725
|
self.device.software_version = software_version
|
|
1726
|
+
invalid_software_image_file = SoftwareImageFile.objects.filter(default_image=False).first()
|
|
1727
|
+
invalid_software_image_file.device_types.set([])
|
|
1728
|
+
self.device.software_image_files.set([invalid_software_image_file])
|
|
1726
1729
|
|
|
1727
|
-
#
|
|
1730
|
+
# There is an invalid non-default software_image_file specified for the software version
|
|
1731
|
+
# It is not a default image and it does not match any device type
|
|
1728
1732
|
with self.assertRaises(ValidationError):
|
|
1729
1733
|
self.device.validated_save()
|
|
1730
1734
|
|
|
1735
|
+
# user should be able to specify any software version without specifying software_image_files
|
|
1736
|
+
self.device.software_image_files.set([])
|
|
1737
|
+
self.device.validated_save()
|
|
1738
|
+
|
|
1731
1739
|
# Default image matches
|
|
1732
1740
|
software_image_file = software_version.software_image_files.first()
|
|
1733
1741
|
software_image_file.default_image = True
|
|
@@ -1903,6 +1911,37 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1903
1911
|
self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
|
|
1904
1912
|
|
|
1905
1913
|
|
|
1914
|
+
class DeviceBayTestCase(ModelTestCases.BaseModelTestCase):
|
|
1915
|
+
model = DeviceBay
|
|
1916
|
+
|
|
1917
|
+
def setUp(self):
|
|
1918
|
+
self.devices = Device.objects.filter(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
|
|
1919
|
+
devicetype = DeviceType.objects.create(
|
|
1920
|
+
manufacturer=self.devices[0].device_type.manufacturer,
|
|
1921
|
+
model="TestDeviceType1",
|
|
1922
|
+
u_height=0,
|
|
1923
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
|
|
1924
|
+
)
|
|
1925
|
+
child_device = Device.objects.create(
|
|
1926
|
+
device_type=devicetype,
|
|
1927
|
+
role=self.devices[0].role,
|
|
1928
|
+
name="TestDevice1",
|
|
1929
|
+
status=self.devices[0].status,
|
|
1930
|
+
location=self.devices[0].location,
|
|
1931
|
+
)
|
|
1932
|
+
DeviceBay.objects.create(device=self.devices[0], name="Device Bay 1", installed_device=child_device)
|
|
1933
|
+
|
|
1934
|
+
def test_assigning_installed_device(self):
|
|
1935
|
+
server = Device.objects.exclude(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD).last()
|
|
1936
|
+
bay = DeviceBay(device=self.devices[1], name="Device Bay Err", installed_device=server)
|
|
1937
|
+
with self.assertRaises(ValidationError) as err:
|
|
1938
|
+
bay.validated_save()
|
|
1939
|
+
self.assertIn(
|
|
1940
|
+
f'Cannot install device "{server}"; device-type "{server.device_type}" subdevice_role is not "child".',
|
|
1941
|
+
str(err.exception),
|
|
1942
|
+
)
|
|
1943
|
+
|
|
1944
|
+
|
|
1906
1945
|
class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
|
|
1907
1946
|
model = DeviceTypeToSoftwareImageFile
|
|
1908
1947
|
|
|
@@ -817,6 +817,7 @@ class DeviceTypeTestCase(
|
|
|
817
817
|
cls.bulk_edit_data = {
|
|
818
818
|
"u_height": 0,
|
|
819
819
|
"is_full_depth": False,
|
|
820
|
+
"comments": "changed comment",
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
def test_list_has_correct_links(self):
|
|
@@ -1196,6 +1197,7 @@ class ModuleTypeTestCase(
|
|
|
1196
1197
|
ModuleType.objects.create(
|
|
1197
1198
|
model="Test Module Type 1",
|
|
1198
1199
|
manufacturer=manufacturers[0],
|
|
1200
|
+
comments="test comment",
|
|
1199
1201
|
)
|
|
1200
1202
|
ModuleType.objects.create(
|
|
1201
1203
|
model="Test Module Type 2",
|
|
@@ -1215,10 +1217,12 @@ class ModuleTypeTestCase(
|
|
|
1215
1217
|
"model": "Test Module Type X",
|
|
1216
1218
|
"part_number": "123ABC",
|
|
1217
1219
|
"tags": [t.pk for t in Tag.objects.get_for_model(ModuleType)],
|
|
1220
|
+
"comments": "test comment",
|
|
1218
1221
|
}
|
|
1219
1222
|
|
|
1220
1223
|
cls.bulk_edit_data = {
|
|
1221
1224
|
"manufacturer": manufacturers[1].pk,
|
|
1225
|
+
"comments": "changed comment",
|
|
1222
1226
|
}
|
|
1223
1227
|
|
|
1224
1228
|
def test_list_has_correct_links(self):
|
|
@@ -2080,7 +2084,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2080
2084
|
status_active = statuses[0]
|
|
2081
2085
|
|
|
2082
2086
|
# We want unique sets of software image files for each device type
|
|
2083
|
-
software_image_files = list(SoftwareImageFile.objects.
|
|
2087
|
+
software_image_files = list(SoftwareImageFile.objects.filter(default_image=False)[:4])
|
|
2084
2088
|
software_versions = list(SoftwareVersion.objects.filter(software_image_files__isnull=False)[:2])
|
|
2085
2089
|
software_image_files[0].software_version = software_versions[0]
|
|
2086
2090
|
software_image_files[1].software_version = software_versions[0]
|
|
@@ -2090,6 +2094,10 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2090
2094
|
software_image_file.save()
|
|
2091
2095
|
devicetypes[0].software_image_files.set(software_image_files[:2])
|
|
2092
2096
|
devicetypes[1].software_image_files.set(software_image_files[2:])
|
|
2097
|
+
# Only valid software image files are those that belong to the device type or default images
|
|
2098
|
+
valid_software_image_files = software_image_files[2:] + [
|
|
2099
|
+
SoftwareImageFile.objects.filter(default_image=True).first()
|
|
2100
|
+
]
|
|
2093
2101
|
|
|
2094
2102
|
cls.custom_fields = (
|
|
2095
2103
|
CustomField.objects.create(
|
|
@@ -2201,7 +2209,7 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2201
2209
|
"cf_crash_counter": -1,
|
|
2202
2210
|
"cr_router-id": None,
|
|
2203
2211
|
"software_version": software_versions[1].pk,
|
|
2204
|
-
"software_image_files": [f.pk for f in
|
|
2212
|
+
"software_image_files": [f.pk for f in valid_software_image_files],
|
|
2205
2213
|
}
|
|
2206
2214
|
|
|
2207
2215
|
cls.bulk_edit_data = {
|
|
@@ -2359,6 +2367,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
2359
2367
|
sorted(interface_ips),
|
|
2360
2368
|
)
|
|
2361
2369
|
|
|
2370
|
+
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
2371
|
+
assign_ip_form_data["pk"] = []
|
|
2372
|
+
assign_ip_request = {
|
|
2373
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
2374
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
2375
|
+
"data": post_data(assign_ip_form_data),
|
|
2376
|
+
}
|
|
2377
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
2378
|
+
self.assertHttpStatus(response, 200)
|
|
2379
|
+
self.assertIn(
|
|
2380
|
+
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2362
2383
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2363
2384
|
def test_device_rearports(self):
|
|
2364
2385
|
device = Device.objects.first()
|
|
@@ -4172,6 +4193,28 @@ class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
4172
4193
|
"domain": "domain-x",
|
|
4173
4194
|
}
|
|
4174
4195
|
|
|
4196
|
+
def test_device_interfaces_count_correct(self):
|
|
4197
|
+
"""
|
|
4198
|
+
This checks whether the other memebers' interfaces are included in the
|
|
4199
|
+
interfaces tab of the master device and whether the interface count on the tab header is
|
|
4200
|
+
rendered correctly.
|
|
4201
|
+
"""
|
|
4202
|
+
self.user.is_superuser = True
|
|
4203
|
+
self.user.save()
|
|
4204
|
+
interface_status = Status.objects.get_for_model(Interface).first()
|
|
4205
|
+
Interface.objects.create(device=self.devices[0], name="eth0", status=interface_status)
|
|
4206
|
+
Interface.objects.create(device=self.devices[0], name="eth1", status=interface_status)
|
|
4207
|
+
Interface.objects.create(device=self.devices[1], name="device 1 interface 1", status=interface_status)
|
|
4208
|
+
Interface.objects.create(device=self.devices[1], name="device 1 interface 2", status=interface_status)
|
|
4209
|
+
Interface.objects.create(device=self.devices[2], name="device 2 interface 1", status=interface_status)
|
|
4210
|
+
Interface.objects.create(device=self.devices[2], name="device 2 interface 2", status=interface_status)
|
|
4211
|
+
response = self.client.get(reverse("dcim:device_interfaces", kwargs={"pk": self.devices[0].pk}))
|
|
4212
|
+
self.assertIn('Interfaces <span class="badge">6</span>', str(response.content))
|
|
4213
|
+
self.assertIn("device 1 interface 1", str(response.content))
|
|
4214
|
+
self.assertIn("device 1 interface 2", str(response.content))
|
|
4215
|
+
self.assertIn("device 2 interface 1", str(response.content))
|
|
4216
|
+
self.assertIn("device 2 interface 2", str(response.content))
|
|
4217
|
+
|
|
4175
4218
|
def test_device_column_visible(self):
|
|
4176
4219
|
"""
|
|
4177
4220
|
This checks whether the device column on a device's interfaces
|
nautobot/dcim/views.py
CHANGED
|
@@ -1923,7 +1923,7 @@ class DeviceInterfacesView(DeviceComponentTabView):
|
|
|
1923
1923
|
|
|
1924
1924
|
def get_extra_context(self, request, instance):
|
|
1925
1925
|
interfaces = (
|
|
1926
|
-
instance.
|
|
1926
|
+
instance.vc_interfaces.restrict(request.user, "view")
|
|
1927
1927
|
.prefetch_related(
|
|
1928
1928
|
Prefetch("ip_addresses", queryset=IPAddress.objects.restrict(request.user)),
|
|
1929
1929
|
Prefetch("member_interfaces", queryset=Interface.objects.restrict(request.user)),
|
|
@@ -4049,6 +4049,9 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
4049
4049
|
devices_table = tables.DeviceTable(devices)
|
|
4050
4050
|
devices_table.columns.show("device_redundancy_group_priority")
|
|
4051
4051
|
context["devices_table"] = devices_table
|
|
4052
|
+
controllers = instance.controllers_sorted.restrict(request.user)
|
|
4053
|
+
controllers_table = tables.ControllerTable(controllers)
|
|
4054
|
+
context["controllers_table"] = controllers_table
|
|
4052
4055
|
return context
|
|
4053
4056
|
|
|
4054
4057
|
|
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
|
|
@@ -492,59 +490,6 @@ class ImageAttachmentViewSet(ModelViewSet):
|
|
|
492
490
|
#
|
|
493
491
|
|
|
494
492
|
|
|
495
|
-
def _create_schedule(serializer, data, job_model, user, approval_required, task_queue=None):
|
|
496
|
-
"""
|
|
497
|
-
This is an internal function to create a scheduled job from API data.
|
|
498
|
-
It has to handle both once-offs (i.e. of type TYPE_FUTURE) and interval
|
|
499
|
-
jobs.
|
|
500
|
-
"""
|
|
501
|
-
type_ = serializer["interval"]
|
|
502
|
-
if type_ == JobExecutionType.TYPE_IMMEDIATELY:
|
|
503
|
-
time = timezone.now()
|
|
504
|
-
name = serializer.get("name") or f"{job_model.name} - {time}"
|
|
505
|
-
elif type_ == JobExecutionType.TYPE_CUSTOM:
|
|
506
|
-
time = serializer.get("start_time") # doing .get("key", "default") returns None instead of "default"
|
|
507
|
-
if time is None:
|
|
508
|
-
# "start_time" is checked against models.ScheduledJob.earliest_possible_time()
|
|
509
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
510
|
-
time = timezone.now() + timedelta(seconds=20)
|
|
511
|
-
name = serializer["name"]
|
|
512
|
-
else:
|
|
513
|
-
time = serializer["start_time"]
|
|
514
|
-
name = serializer["name"]
|
|
515
|
-
crontab = serializer.get("crontab", "")
|
|
516
|
-
|
|
517
|
-
celery_kwargs = {
|
|
518
|
-
"nautobot_job_profile": False,
|
|
519
|
-
"queue": task_queue,
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
# 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
|
|
523
|
-
#
|
|
524
|
-
# We pass in task and job_model here partly for forward/backward compatibility logic, and
|
|
525
|
-
# part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
|
|
526
|
-
# FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
|
|
527
|
-
# null) you still have a bit of context on the ScheduledJob as to what it was originally
|
|
528
|
-
# scheduled for.
|
|
529
|
-
scheduled_job = ScheduledJob(
|
|
530
|
-
name=name,
|
|
531
|
-
task=job_model.class_path,
|
|
532
|
-
job_model=job_model,
|
|
533
|
-
start_time=time,
|
|
534
|
-
description=f"Nautobot job {name} scheduled by {user} for {time}",
|
|
535
|
-
kwargs=data,
|
|
536
|
-
celery_kwargs=celery_kwargs,
|
|
537
|
-
interval=type_,
|
|
538
|
-
one_off=(type_ == JobExecutionType.TYPE_FUTURE),
|
|
539
|
-
user=user,
|
|
540
|
-
approval_required=approval_required,
|
|
541
|
-
crontab=crontab,
|
|
542
|
-
queue=task_queue,
|
|
543
|
-
)
|
|
544
|
-
scheduled_job.validated_save()
|
|
545
|
-
return scheduled_job
|
|
546
|
-
|
|
547
|
-
|
|
548
493
|
class JobViewSetBase(
|
|
549
494
|
NautobotAPIVersionMixin,
|
|
550
495
|
# note no CreateModelMixin
|
|
@@ -744,13 +689,16 @@ class JobViewSetBase(
|
|
|
744
689
|
|
|
745
690
|
# Try to create a ScheduledJob, or...
|
|
746
691
|
if schedule_data:
|
|
747
|
-
schedule =
|
|
748
|
-
schedule_data,
|
|
749
|
-
job_class.serialize_data(cleaned_data),
|
|
692
|
+
schedule = ScheduledJob.create_schedule(
|
|
750
693
|
job_model,
|
|
751
694
|
request.user,
|
|
752
|
-
|
|
695
|
+
name=schedule_data.get("name"),
|
|
696
|
+
start_time=schedule_data.get("start_time"),
|
|
697
|
+
interval=schedule_data.get("interval"),
|
|
698
|
+
crontab=schedule_data.get("crontab", ""),
|
|
699
|
+
approval_required=approval_required,
|
|
753
700
|
task_queue=input_serializer.validated_data.get("task_queue", None),
|
|
701
|
+
**job_class.serialize_data(cleaned_data),
|
|
754
702
|
)
|
|
755
703
|
else:
|
|
756
704
|
schedule = None
|
nautobot/extras/factory.py
CHANGED
|
@@ -251,6 +251,24 @@ class MetadataTypeFactory(PrimaryModelFactory):
|
|
|
251
251
|
)
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
def _available_field_names(metadata_type, assigned_object):
|
|
255
|
+
field_names = [field.name for field in assigned_object._meta.get_fields()]
|
|
256
|
+
# Avoid collisions, see ObjectMetadata.clean()
|
|
257
|
+
existing_metadata_scoped_fields = ObjectMetadata.objects.filter(
|
|
258
|
+
metadata_type=metadata_type,
|
|
259
|
+
assigned_object_type=ContentType.objects.get_for_model(assigned_object),
|
|
260
|
+
assigned_object_id=assigned_object.pk,
|
|
261
|
+
).values_list("scoped_fields", flat=True)
|
|
262
|
+
for existing_scoped_fields in existing_metadata_scoped_fields:
|
|
263
|
+
if existing_scoped_fields:
|
|
264
|
+
field_names = sorted(set(field_names).difference(existing_scoped_fields))
|
|
265
|
+
else:
|
|
266
|
+
field_names = []
|
|
267
|
+
break
|
|
268
|
+
|
|
269
|
+
return field_names
|
|
270
|
+
|
|
271
|
+
|
|
254
272
|
class ObjectMetadataFactory(BaseModelFactory):
|
|
255
273
|
"""ObjectMetadata model factory"""
|
|
256
274
|
|
|
@@ -263,7 +281,6 @@ class ObjectMetadataFactory(BaseModelFactory):
|
|
|
263
281
|
MetadataType.objects.all(),
|
|
264
282
|
allow_null=False,
|
|
265
283
|
)
|
|
266
|
-
scoped_fields = factory.Faker("pylist", allowed_types=[str])
|
|
267
284
|
|
|
268
285
|
@factory.lazy_attribute
|
|
269
286
|
def contact(self):
|
|
@@ -308,22 +325,43 @@ class ObjectMetadataFactory(BaseModelFactory):
|
|
|
308
325
|
raise RuntimeError(f"Unsupported metadatatype datatype {metadata_type_data_type}")
|
|
309
326
|
|
|
310
327
|
@factory.lazy_attribute
|
|
311
|
-
def
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
content_type = factory.random.randgen.choice(
|
|
315
|
-
ContentType.objects.filter(FeatureQuery("metadata").get_query(), pk__in=allowed_content_types)
|
|
316
|
-
)
|
|
328
|
+
def assigned_object(self):
|
|
329
|
+
allowed_content_types = list(self.metadata_type.content_types.all())
|
|
330
|
+
for content_type in factory.random.randgen.sample(allowed_content_types, len(allowed_content_types)):
|
|
317
331
|
# It does not have a get_absolute_url attribute and is causing failure in API unittests
|
|
318
332
|
if content_type.app_label == "extras" and content_type.model == "taggeditem":
|
|
319
333
|
continue
|
|
320
|
-
|
|
321
|
-
|
|
334
|
+
|
|
335
|
+
assigned_model = content_type.model_class()
|
|
336
|
+
queryset = assigned_model.objects.all()
|
|
337
|
+
|
|
338
|
+
if not queryset.exists():
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
for _ in range(10):
|
|
342
|
+
assigned_object = factory.random.randgen.choice(queryset)
|
|
343
|
+
if _available_field_names(self.metadata_type, assigned_object):
|
|
344
|
+
return assigned_object
|
|
345
|
+
|
|
346
|
+
raise RuntimeError(f"Couldn't find any suitable instances not already covered by {self.metadata_type}")
|
|
322
347
|
|
|
323
348
|
@factory.lazy_attribute
|
|
324
|
-
def
|
|
325
|
-
|
|
326
|
-
|
|
349
|
+
def scoped_fields(self):
|
|
350
|
+
all_field_names = [field.name for field in self.assigned_object._meta.get_fields()]
|
|
351
|
+
field_names = _available_field_names(self.metadata_type, self.assigned_object)
|
|
352
|
+
if not field_names:
|
|
353
|
+
raise RuntimeError(
|
|
354
|
+
f"All existing scoped_fields for {self.metadata_type} are covered by existing ObjectMetadata for {self.assigned_object}"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if len(field_names) < len(all_field_names):
|
|
358
|
+
minimum_fields = 1 # don't allow an empty list since that would cover all fields
|
|
359
|
+
else:
|
|
360
|
+
minimum_fields = 0
|
|
361
|
+
|
|
362
|
+
return factory.random.randgen.sample(
|
|
363
|
+
field_names, k=factory.random.randgen.randint(minimum_fields, len(field_names))
|
|
364
|
+
)
|
|
327
365
|
|
|
328
366
|
|
|
329
367
|
class ObjectChangeFactory(BaseModelFactory):
|
|
@@ -181,6 +181,7 @@ class ComputedFieldFilterSet(BaseFilterSet):
|
|
|
181
181
|
"description": "icontains",
|
|
182
182
|
"content_type__app_label": "icontains",
|
|
183
183
|
"content_type__model": "icontains",
|
|
184
|
+
"grouping": "icontains",
|
|
184
185
|
"template": "icontains",
|
|
185
186
|
"fallback_value": "icontains",
|
|
186
187
|
},
|
|
@@ -192,6 +193,7 @@ class ComputedFieldFilterSet(BaseFilterSet):
|
|
|
192
193
|
fields = (
|
|
193
194
|
"content_type",
|
|
194
195
|
"key",
|
|
196
|
+
"grouping",
|
|
195
197
|
"template",
|
|
196
198
|
"fallback_value",
|
|
197
199
|
"weight",
|
|
@@ -422,6 +424,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|
|
422
424
|
filter_predicates={
|
|
423
425
|
"label": "icontains",
|
|
424
426
|
"description": "icontains",
|
|
427
|
+
"grouping": "icontains",
|
|
425
428
|
},
|
|
426
429
|
)
|
|
427
430
|
content_types = ContentTypeMultipleChoiceFilter(
|
|
@@ -430,7 +433,7 @@ class CustomFieldFilterSet(BaseFilterSet):
|
|
|
430
433
|
|
|
431
434
|
class Meta:
|
|
432
435
|
model = CustomField
|
|
433
|
-
fields = ["id", "content_types", "label", "required", "filter_logic", "weight"]
|
|
436
|
+
fields = ["id", "content_types", "label", "grouping", "required", "filter_logic", "weight"]
|
|
434
437
|
|
|
435
438
|
|
|
436
439
|
class CustomFieldChoiceFilterSet(BaseFilterSet):
|
nautobot/extras/forms/base.py
CHANGED
|
@@ -26,11 +26,12 @@ __all__ = (
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class NautobotModelForm(
|
|
29
|
+
BootstrapMixin,
|
|
30
|
+
# The below must be listed *after* BootstrapMixin so that BootstrapMixin applies to their dynamic form fields
|
|
29
31
|
CustomFieldModelFormMixin,
|
|
30
32
|
DynamicGroupModelFormMixin,
|
|
31
33
|
NoteModelFormMixin,
|
|
32
34
|
RelationshipModelFormMixin,
|
|
33
|
-
BootstrapMixin,
|
|
34
35
|
):
|
|
35
36
|
"""
|
|
36
37
|
This class exists to combine common functionality and is used to inherit from throughout the
|
|
@@ -40,9 +41,10 @@ class NautobotModelForm(
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class NautobotFilterForm(
|
|
43
|
-
ContactTeamModelFilterFormMixin,
|
|
44
44
|
BootstrapMixin,
|
|
45
|
-
|
|
45
|
+
# The below must be listed *after* BootstrapMixin so that BootstrapMixin applies to their dynamic form fields
|
|
46
|
+
ContactTeamModelFilterFormMixin,
|
|
47
|
+
CustomFieldModelFilterFormMixin,
|
|
46
48
|
RelationshipModelFilterFormMixin,
|
|
47
49
|
):
|
|
48
50
|
"""
|
|
@@ -53,6 +55,10 @@ class NautobotFilterForm(
|
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class NautobotBulkEditForm(
|
|
56
|
-
BootstrapMixin,
|
|
58
|
+
BootstrapMixin,
|
|
59
|
+
# The below must be listed *after* BootstrapMixin so that BootstrapMixin applies to their dynamic form fields
|
|
60
|
+
CustomFieldModelBulkEditFormMixin,
|
|
61
|
+
NoteModelBulkEditFormMixin,
|
|
62
|
+
RelationshipModelBulkEditFormMixin,
|
|
57
63
|
):
|
|
58
64
|
"""Base class for bulk-edit forms for models that support relationships, custom fields and notes."""
|
nautobot/extras/forms/forms.py
CHANGED
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):
|