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
|
@@ -1352,7 +1352,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1352
1352
|
value="Invalid assigned object type",
|
|
1353
1353
|
scoped_fields=["status"],
|
|
1354
1354
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1355
|
-
assigned_object_id=Contact.objects.first().pk,
|
|
1355
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1356
1356
|
)
|
|
1357
1357
|
obj_metadata.validated_save()
|
|
1358
1358
|
|
|
@@ -1362,29 +1362,29 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1362
1362
|
)
|
|
1363
1363
|
type_contact_team.content_types.add(ContentType.objects.get_for_model(Contact))
|
|
1364
1364
|
type_contact_team.content_types.add(ContentType.objects.get_for_model(Team))
|
|
1365
|
-
instance1 = ObjectMetadata
|
|
1365
|
+
instance1 = ObjectMetadata(
|
|
1366
1366
|
metadata_type=type_contact_team,
|
|
1367
1367
|
contact=Contact.objects.first(),
|
|
1368
1368
|
team=Team.objects.first(),
|
|
1369
1369
|
scoped_fields=["address"],
|
|
1370
1370
|
assigned_object_type=ContentType.objects.get_for_model(Contact),
|
|
1371
|
-
assigned_object_id=Contact.objects.first().pk,
|
|
1371
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1372
1372
|
)
|
|
1373
|
-
instance2 = ObjectMetadata
|
|
1373
|
+
instance2 = ObjectMetadata(
|
|
1374
1374
|
metadata_type=type_contact_team,
|
|
1375
1375
|
contact=None,
|
|
1376
1376
|
team=None,
|
|
1377
1377
|
scoped_fields=["phone"],
|
|
1378
1378
|
assigned_object_type=ContentType.objects.get_for_model(Contact),
|
|
1379
|
-
assigned_object_id=Contact.objects.last().pk,
|
|
1379
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).last().pk,
|
|
1380
1380
|
)
|
|
1381
|
-
instance3 = ObjectMetadata
|
|
1381
|
+
instance3 = ObjectMetadata(
|
|
1382
1382
|
metadata_type=type_contact_team,
|
|
1383
1383
|
contact=Contact.objects.first(),
|
|
1384
1384
|
team=None,
|
|
1385
1385
|
scoped_fields=["email"],
|
|
1386
1386
|
assigned_object_type=ContentType.objects.get_for_model(Team),
|
|
1387
|
-
assigned_object_id=Team.objects.first().pk,
|
|
1387
|
+
assigned_object_id=Team.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1388
1388
|
)
|
|
1389
1389
|
with self.assertRaises(ValidationError):
|
|
1390
1390
|
instance1.validated_save()
|
|
@@ -1407,7 +1407,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1407
1407
|
value="Some text value",
|
|
1408
1408
|
scoped_fields=["status", "parent"],
|
|
1409
1409
|
assigned_object_type=obj_type,
|
|
1410
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1410
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1411
1411
|
)
|
|
1412
1412
|
obj_metadata.save()
|
|
1413
1413
|
|
|
@@ -1440,7 +1440,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1440
1440
|
value=15,
|
|
1441
1441
|
scoped_fields=["status", "parent"],
|
|
1442
1442
|
assigned_object_type=obj_type,
|
|
1443
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1443
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1444
1444
|
)
|
|
1445
1445
|
obj_metadata.validated_save()
|
|
1446
1446
|
|
|
@@ -1484,7 +1484,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1484
1484
|
value=15.245,
|
|
1485
1485
|
scoped_fields=["status", "parent"],
|
|
1486
1486
|
assigned_object_type=obj_type,
|
|
1487
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1487
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1488
1488
|
)
|
|
1489
1489
|
obj_metadata.validated_save()
|
|
1490
1490
|
|
|
@@ -1525,7 +1525,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1525
1525
|
value=False,
|
|
1526
1526
|
scoped_fields=["status", "parent"],
|
|
1527
1527
|
assigned_object_type=obj_type,
|
|
1528
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1528
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1529
1529
|
)
|
|
1530
1530
|
obj_metadata.validated_save()
|
|
1531
1531
|
|
|
@@ -1559,7 +1559,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1559
1559
|
value="1994-01-01",
|
|
1560
1560
|
scoped_fields=["status", "parent"],
|
|
1561
1561
|
assigned_object_type=obj_type,
|
|
1562
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1562
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1563
1563
|
)
|
|
1564
1564
|
obj_metadata.validated_save()
|
|
1565
1565
|
|
|
@@ -1596,7 +1596,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1596
1596
|
value="2024-06-27T17:58:47-0500",
|
|
1597
1597
|
scoped_fields=["status", "parent"],
|
|
1598
1598
|
assigned_object_type=obj_type,
|
|
1599
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1599
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1600
1600
|
)
|
|
1601
1601
|
obj_metadata.validated_save()
|
|
1602
1602
|
|
|
@@ -1663,7 +1663,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1663
1663
|
value="Option A",
|
|
1664
1664
|
scoped_fields=["status", "parent"],
|
|
1665
1665
|
assigned_object_type=obj_type,
|
|
1666
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1666
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1667
1667
|
)
|
|
1668
1668
|
obj_metadata.validated_save()
|
|
1669
1669
|
|
|
@@ -1689,7 +1689,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1689
1689
|
value=["Option A"],
|
|
1690
1690
|
scoped_fields=["status", "parent"],
|
|
1691
1691
|
assigned_object_type=obj_type,
|
|
1692
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1692
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1693
1693
|
)
|
|
1694
1694
|
obj_metadata.validated_save()
|
|
1695
1695
|
|
|
@@ -1700,20 +1700,22 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1700
1700
|
self.assertIn(f"Invalid choice(s) ({invalid_options})", str(context.exception))
|
|
1701
1701
|
|
|
1702
1702
|
def test_no_scoped_fields_overlap(self):
|
|
1703
|
-
"""
|
|
1703
|
+
"""
|
|
1704
|
+
Test that overlapping scoped_fields of ObjectMetadata with same metadata_type/assigned_object is not allowed.
|
|
1705
|
+
"""
|
|
1704
1706
|
ObjectMetadata.objects.create(
|
|
1705
1707
|
metadata_type=MetadataType.objects.first(),
|
|
1706
1708
|
contact=Contact.objects.first(),
|
|
1707
1709
|
scoped_fields=["host", "mask_length", "type", "role", "status"],
|
|
1708
1710
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1709
|
-
assigned_object_id=IPAddress.objects.first().pk,
|
|
1711
|
+
assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1710
1712
|
)
|
|
1711
1713
|
instance2 = ObjectMetadata.objects.create(
|
|
1712
1714
|
metadata_type=MetadataType.objects.first(),
|
|
1713
1715
|
contact=Contact.objects.first(),
|
|
1714
1716
|
scoped_fields=[],
|
|
1715
1717
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1716
|
-
assigned_object_id=IPAddress.objects.first().pk,
|
|
1718
|
+
assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1717
1719
|
)
|
|
1718
1720
|
with self.assertRaises(ValidationError):
|
|
1719
1721
|
# try scope all fields
|
|
@@ -2594,7 +2594,7 @@ class JobTestCase(
|
|
|
2594
2594
|
|
|
2595
2595
|
self.assertInHTML('<option value="uniquequeue" selected>', content)
|
|
2596
2596
|
self.assertInHTML(
|
|
2597
|
-
'<input type="text" name="var" value="456" class="form-control
|
|
2597
|
+
'<input type="text" name="var" value="456" class="form-control" required placeholder="None" id="id_var">',
|
|
2598
2598
|
content,
|
|
2599
2599
|
)
|
|
2600
2600
|
self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
|
|
@@ -2994,6 +2994,27 @@ class JobButtonRenderingTestCase(TestCase):
|
|
|
2994
2994
|
)
|
|
2995
2995
|
|
|
2996
2996
|
|
|
2997
|
+
class JobCustomTemplateTestCase(TestCase):
|
|
2998
|
+
@classmethod
|
|
2999
|
+
def setUpTestData(cls):
|
|
3000
|
+
# Job model objects are automatically created during database migrations
|
|
3001
|
+
|
|
3002
|
+
# But we do need to make sure the ones we're testing are flagged appropriately
|
|
3003
|
+
cls.example_job = Job.objects.get(job_class_name="ExampleCustomFormJob")
|
|
3004
|
+
cls.example_job.enabled = True
|
|
3005
|
+
cls.example_job.save()
|
|
3006
|
+
|
|
3007
|
+
cls.run_url = reverse("extras:job_run", kwargs={"pk": cls.example_job.pk})
|
|
3008
|
+
|
|
3009
|
+
def test_rendering_custom_template(self):
|
|
3010
|
+
obj_perm = ObjectPermission(name="Test permission", actions=["view", "run"])
|
|
3011
|
+
obj_perm.save()
|
|
3012
|
+
obj_perm.users.add(self.user)
|
|
3013
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
|
|
3014
|
+
with self.assertTemplateUsed("example_app/custom_job_form.html"):
|
|
3015
|
+
self.client.get(self.run_url)
|
|
3016
|
+
|
|
3017
|
+
|
|
2997
3018
|
# TODO: Convert to StandardTestCases.Views
|
|
2998
3019
|
class ObjectChangeTestCase(TestCase):
|
|
2999
3020
|
user_permissions = ("extras.view_objectchange",)
|
|
@@ -3029,8 +3050,7 @@ class ObjectChangeTestCase(TestCase):
|
|
|
3029
3050
|
|
|
3030
3051
|
|
|
3031
3052
|
class ObjectMetadataTestCase(
|
|
3032
|
-
ViewTestCases.
|
|
3033
|
-
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
3053
|
+
ViewTestCases.GetObjectViewTestCase,
|
|
3034
3054
|
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
3035
3055
|
ViewTestCases.ListObjectsViewTestCase,
|
|
3036
3056
|
):
|
nautobot/extras/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import contextlib
|
|
2
3
|
import hashlib
|
|
3
4
|
import hmac
|
|
4
5
|
import logging
|
|
@@ -14,6 +15,7 @@ from django.db import transaction
|
|
|
14
15
|
from django.db.models import Q
|
|
15
16
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
16
17
|
from django.utils.deconstruct import deconstructible
|
|
18
|
+
import redis.exceptions
|
|
17
19
|
|
|
18
20
|
from nautobot.core.choices import ColorChoices
|
|
19
21
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
@@ -109,12 +111,17 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
|
|
|
109
111
|
def change_logged_models_queryset():
|
|
110
112
|
"""
|
|
111
113
|
Cacheable function for cases where we need this queryset many times, such as when saving multiple objects.
|
|
114
|
+
|
|
115
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
112
116
|
"""
|
|
117
|
+
queryset = None
|
|
113
118
|
cache_key = "nautobot.extras.utils.change_logged_models_queryset"
|
|
114
|
-
|
|
119
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
120
|
+
queryset = cache.get(cache_key)
|
|
115
121
|
if queryset is None:
|
|
116
122
|
queryset = ChangeLoggedModelsQuery().as_queryset()
|
|
117
|
-
|
|
123
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
124
|
+
cache.set(cache_key, queryset)
|
|
118
125
|
return queryset
|
|
119
126
|
|
|
120
127
|
|
|
@@ -160,7 +167,7 @@ class FeatureQuery:
|
|
|
160
167
|
"""
|
|
161
168
|
Given an extras feature, return a iterable of app_label: [models] for content type lookup.
|
|
162
169
|
|
|
163
|
-
|
|
170
|
+
Misnamed, as it returns an iterable of (key, value) (i.e. dict.items()) rather than an actual dict.
|
|
164
171
|
|
|
165
172
|
Raises a KeyError if the given feature doesn't exist.
|
|
166
173
|
"""
|
|
@@ -173,12 +180,34 @@ class FeatureQuery:
|
|
|
173
180
|
|
|
174
181
|
>>> FeatureQuery('statuses').get_choices()
|
|
175
182
|
[('dcim.device', 13), ('dcim.rack', 34)]
|
|
183
|
+
|
|
184
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
176
185
|
"""
|
|
177
|
-
|
|
186
|
+
choices = None
|
|
187
|
+
cache_key = f"nautobot.extras.utils.FeatureQuery.choices.{self.feature}"
|
|
188
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
189
|
+
choices = cache.get(cache_key)
|
|
190
|
+
if choices is None:
|
|
191
|
+
choices = [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
|
|
192
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
193
|
+
cache.set(cache_key, choices)
|
|
194
|
+
return choices
|
|
178
195
|
|
|
179
196
|
def list_subclasses(self):
|
|
180
|
-
"""
|
|
181
|
-
|
|
197
|
+
"""
|
|
198
|
+
Return a list of model classes that declare this feature.
|
|
199
|
+
|
|
200
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
201
|
+
"""
|
|
202
|
+
subclasses = None
|
|
203
|
+
cache_key = f"nautobot.extras.utils.FeatureQuery.subclasses.{self.feature}"
|
|
204
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
205
|
+
subclasses = cache.get(cache_key)
|
|
206
|
+
if subclasses is None:
|
|
207
|
+
subclasses = [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
|
|
208
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
209
|
+
cache.set(cache_key, subclasses)
|
|
210
|
+
return subclasses
|
|
182
211
|
|
|
183
212
|
|
|
184
213
|
@deconstructible
|
nautobot/extras/views.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
1
|
import logging
|
|
3
2
|
from urllib.parse import parse_qs
|
|
4
3
|
|
|
@@ -1388,55 +1387,25 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
1388
1387
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
1389
1388
|
|
|
1390
1389
|
if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
# as a once in the future task with the due date set to the current time. This means
|
|
1397
|
-
# when approval is granted, the task is immediately due for execution.
|
|
1398
|
-
schedule_type = JobExecutionType.TYPE_FUTURE
|
|
1399
|
-
schedule_datetime = timezone.now()
|
|
1400
|
-
schedule_name = f"{job_model} - {schedule_datetime}"
|
|
1401
|
-
|
|
1402
|
-
else:
|
|
1403
|
-
schedule_name = schedule_form.cleaned_data["_schedule_name"]
|
|
1404
|
-
|
|
1405
|
-
if schedule_type == JobExecutionType.TYPE_CUSTOM:
|
|
1406
|
-
crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
|
|
1407
|
-
# doing .get("key", "default") returns None instead of "default" here for some reason
|
|
1408
|
-
schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
|
|
1409
|
-
if schedule_datetime is None:
|
|
1410
|
-
# "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
|
|
1411
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
1412
|
-
schedule_datetime = timezone.now() + timedelta(seconds=20)
|
|
1413
|
-
else:
|
|
1414
|
-
schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
|
|
1415
|
-
|
|
1416
|
-
celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
|
|
1417
|
-
scheduled_job = ScheduledJob(
|
|
1418
|
-
name=schedule_name,
|
|
1419
|
-
task=job_model.class_path,
|
|
1420
|
-
job_model=job_model,
|
|
1421
|
-
start_time=schedule_datetime,
|
|
1422
|
-
description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
|
|
1423
|
-
kwargs=job_class.serialize_data(job_form.cleaned_data),
|
|
1424
|
-
celery_kwargs=celery_kwargs,
|
|
1390
|
+
scheduled_job = ScheduledJob.create_schedule(
|
|
1391
|
+
job_model,
|
|
1392
|
+
request.user,
|
|
1393
|
+
name=schedule_form.cleaned_data.get("_schedule_name"),
|
|
1394
|
+
start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
|
|
1425
1395
|
interval=schedule_type,
|
|
1426
|
-
|
|
1427
|
-
queue=task_queue,
|
|
1428
|
-
user=request.user,
|
|
1396
|
+
crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
|
|
1429
1397
|
approval_required=job_model.approval_required,
|
|
1430
|
-
|
|
1398
|
+
task_queue=task_queue,
|
|
1399
|
+
profile=profile,
|
|
1400
|
+
**job_class.serialize_data(job_form.cleaned_data),
|
|
1431
1401
|
)
|
|
1432
|
-
scheduled_job.validated_save()
|
|
1433
1402
|
|
|
1434
1403
|
if job_model.approval_required:
|
|
1435
|
-
messages.success(request, f"Job {
|
|
1436
|
-
return redirect(return_url
|
|
1404
|
+
messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
|
|
1405
|
+
return redirect(return_url or "extras:scheduledjob_approval_queue_list")
|
|
1437
1406
|
else:
|
|
1438
|
-
messages.success(request, f"Job {
|
|
1439
|
-
return redirect(return_url
|
|
1407
|
+
messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
|
|
1408
|
+
return redirect(return_url or "extras:scheduledjob_list")
|
|
1440
1409
|
|
|
1441
1410
|
else:
|
|
1442
1411
|
# Enqueue job for immediate execution
|
|
@@ -1722,7 +1691,12 @@ class SavedViewUIViewSet(
|
|
|
1722
1691
|
view_name = new_global_default_view.view
|
|
1723
1692
|
message = ""
|
|
1724
1693
|
if new_global_default_view.is_global_default:
|
|
1725
|
-
message
|
|
1694
|
+
message = format_html(
|
|
1695
|
+
'<br>The global default saved view for "{}" is set to <a href="{}">{}</a>',
|
|
1696
|
+
view_name,
|
|
1697
|
+
new_global_default_view.get_absolute_url(),
|
|
1698
|
+
new_global_default_view.name,
|
|
1699
|
+
)
|
|
1726
1700
|
return message
|
|
1727
1701
|
|
|
1728
1702
|
def list(self, request, *args, **kwargs):
|
|
@@ -2076,8 +2050,13 @@ class JobLogEntryTableView(generic.GenericView):
|
|
|
2076
2050
|
else:
|
|
2077
2051
|
queryset = instance.job_log_entries.all()
|
|
2078
2052
|
log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
|
|
2079
|
-
|
|
2080
|
-
|
|
2053
|
+
paginate = {
|
|
2054
|
+
"paginator_class": EnhancedPaginator,
|
|
2055
|
+
"per_page": get_paginate_count(request),
|
|
2056
|
+
}
|
|
2057
|
+
RequestConfig(request, paginate).configure(log_table)
|
|
2058
|
+
table = log_table.as_html(request)
|
|
2059
|
+
return HttpResponse(table)
|
|
2081
2060
|
|
|
2082
2061
|
|
|
2083
2062
|
#
|
|
@@ -2237,15 +2216,13 @@ class MetadataTypeUIViewSet(NautobotUIViewSet):
|
|
|
2237
2216
|
|
|
2238
2217
|
|
|
2239
2218
|
class ObjectMetadataUIViewSet(
|
|
2240
|
-
ObjectBulkDestroyViewMixin,
|
|
2241
2219
|
ObjectChangeLogViewMixin,
|
|
2242
|
-
ObjectDestroyViewMixin,
|
|
2243
2220
|
ObjectDetailViewMixin,
|
|
2244
2221
|
ObjectListViewMixin,
|
|
2245
2222
|
):
|
|
2246
2223
|
filterset_class = filters.ObjectMetadataFilterSet
|
|
2247
2224
|
filterset_form_class = forms.ObjectMetadataFilterForm
|
|
2248
|
-
queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "scoped_fields")
|
|
2225
|
+
queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "assigned_object_id", "scoped_fields")
|
|
2249
2226
|
serializer_class = serializers.ObjectMetadataSerializer
|
|
2250
2227
|
table_class = tables.ObjectMetadataTable
|
|
2251
2228
|
action_buttons = ("export",)
|
|
@@ -2444,7 +2421,7 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2444
2421
|
|
|
2445
2422
|
if ContentType.objects.get_for_model(Prefix) in context["content_types"]:
|
|
2446
2423
|
prefixes = instance.prefixes.restrict(request.user, "view")
|
|
2447
|
-
prefix_table = PrefixTable(prefixes)
|
|
2424
|
+
prefix_table = PrefixTable(prefixes, hide_hierarchy_ui=True)
|
|
2448
2425
|
prefix_table.columns.hide("role")
|
|
2449
2426
|
RequestConfig(request, paginate).configure(prefix_table)
|
|
2450
2427
|
context["prefix_table"] = prefix_table
|
nautobot/ipam/filters.py
CHANGED
|
@@ -498,7 +498,7 @@ class IPAddressFilterSet(
|
|
|
498
498
|
return queryset.none()
|
|
499
499
|
interface_ids = []
|
|
500
500
|
for device in devices:
|
|
501
|
-
interface_ids.extend(device.
|
|
501
|
+
interface_ids.extend(device.vc_interfaces.values_list("id", flat=True))
|
|
502
502
|
return queryset.filter(interfaces__in=interface_ids)
|
|
503
503
|
|
|
504
504
|
def filter_virtual_machine(self, queryset, name, value):
|
nautobot/ipam/forms.py
CHANGED
|
@@ -899,7 +899,7 @@ class ServiceForm(NautobotModelForm):
|
|
|
899
899
|
# Limit IP address choices to those assigned to interfaces of the parent device/VM
|
|
900
900
|
if self.instance.device:
|
|
901
901
|
self.fields["ip_addresses"].queryset = IPAddress.objects.filter(
|
|
902
|
-
interfaces__in=self.instance.device.
|
|
902
|
+
interfaces__in=self.instance.device.vc_interfaces.values_list("id", flat=True)
|
|
903
903
|
)
|
|
904
904
|
elif self.instance.virtual_machine:
|
|
905
905
|
self.fields["ip_addresses"].queryset = IPAddress.objects.filter(
|
nautobot/ipam/models.py
CHANGED
|
@@ -49,7 +49,6 @@ logger = logging.getLogger(__name__)
|
|
|
49
49
|
@extras_features(
|
|
50
50
|
"custom_links",
|
|
51
51
|
"custom_validators",
|
|
52
|
-
"dynamic_groups",
|
|
53
52
|
"export_templates",
|
|
54
53
|
"graphql",
|
|
55
54
|
"locations",
|
|
@@ -399,7 +398,6 @@ class RIR(OrganizationalModel):
|
|
|
399
398
|
@extras_features(
|
|
400
399
|
"custom_links",
|
|
401
400
|
"custom_validators",
|
|
402
|
-
"dynamic_groups",
|
|
403
401
|
"export_templates",
|
|
404
402
|
"graphql",
|
|
405
403
|
"locations",
|
|
@@ -501,11 +499,6 @@ class Prefix(PrimaryModel):
|
|
|
501
499
|
"type",
|
|
502
500
|
"vlan",
|
|
503
501
|
]
|
|
504
|
-
"""
|
|
505
|
-
dynamic_group_filter_fields = {
|
|
506
|
-
"vrf": "vrf_id", # Duplicate filter fields that will be collapsed in 2.0
|
|
507
|
-
}
|
|
508
|
-
"""
|
|
509
502
|
|
|
510
503
|
class Meta:
|
|
511
504
|
ordering = (
|
|
@@ -986,7 +979,6 @@ class PrefixLocationAssignment(BaseModel):
|
|
|
986
979
|
@extras_features(
|
|
987
980
|
"custom_links",
|
|
988
981
|
"custom_validators",
|
|
989
|
-
"dynamic_groups",
|
|
990
982
|
"export_templates",
|
|
991
983
|
"graphql",
|
|
992
984
|
"statuses",
|
|
@@ -1020,7 +1012,7 @@ class IPAddress(PrimaryModel):
|
|
|
1020
1012
|
parent = models.ForeignKey(
|
|
1021
1013
|
"ipam.Prefix",
|
|
1022
1014
|
blank=True,
|
|
1023
|
-
null=True,
|
|
1015
|
+
null=True, # TODO remove this, it shouldn't be permitted for the database!
|
|
1024
1016
|
related_name="ip_addresses", # `IPAddress` to use `related_name="ip_addresses"`
|
|
1025
1017
|
on_delete=models.PROTECT,
|
|
1026
1018
|
help_text="The parent Prefix of this IPAddress.",
|
|
@@ -1117,7 +1109,7 @@ class IPAddress(PrimaryModel):
|
|
|
1117
1109
|
raise ValidationError({"namespace": "No suitable parent Prefix exists in this Namespace"}) from e
|
|
1118
1110
|
|
|
1119
1111
|
def clean(self):
|
|
1120
|
-
|
|
1112
|
+
self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
|
|
1121
1113
|
|
|
1122
1114
|
# Validate that host is not being modified
|
|
1123
1115
|
if self.present_in_database:
|
|
@@ -1131,8 +1123,8 @@ class IPAddress(PrimaryModel):
|
|
|
1131
1123
|
|
|
1132
1124
|
closest_parent = self._get_closest_parent()
|
|
1133
1125
|
# Validate `parent` can be used as the parent for this ipaddress
|
|
1134
|
-
if
|
|
1135
|
-
if self.parent != closest_parent:
|
|
1126
|
+
if closest_parent is not None:
|
|
1127
|
+
if self.parent is not None and self.parent != closest_parent:
|
|
1136
1128
|
raise ValidationError(
|
|
1137
1129
|
{
|
|
1138
1130
|
"parent": (
|
|
@@ -1144,23 +1136,20 @@ class IPAddress(PrimaryModel):
|
|
|
1144
1136
|
self.parent = closest_parent
|
|
1145
1137
|
self._namespace = None
|
|
1146
1138
|
|
|
1147
|
-
def save(self, *args, **kwargs):
|
|
1148
1139
|
# 3.0 TODO: uncomment the below to enforce this constraint
|
|
1149
1140
|
# if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
|
|
1150
1141
|
# err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
|
|
1151
1142
|
# raise ValidationError({"address": err_msg})
|
|
1152
1143
|
|
|
1153
|
-
self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
|
|
1154
|
-
|
|
1155
1144
|
# Force dns_name to lowercase
|
|
1156
1145
|
if not self.dns_name.islower:
|
|
1157
1146
|
self.dns_name = self.dns_name.lower()
|
|
1158
1147
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1148
|
+
super().clean()
|
|
1149
|
+
|
|
1150
|
+
def save(self, *args, **kwargs):
|
|
1151
|
+
self.clean() # MUST do data fixup as above
|
|
1152
|
+
|
|
1164
1153
|
super().save(*args, **kwargs)
|
|
1165
1154
|
|
|
1166
1155
|
@property
|
nautobot/ipam/querysets.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
3
|
from django.core.exceptions import ValidationError
|
|
4
|
+
from django.core.validators import validate_ipv46_address
|
|
4
5
|
from django.db.models import ProtectedError, Q
|
|
5
6
|
import netaddr
|
|
6
7
|
|
|
@@ -397,6 +398,31 @@ class IPAddressQuerySet(BaseNetworkQuerySet):
|
|
|
397
398
|
"""
|
|
398
399
|
return super().order_by("host")
|
|
399
400
|
|
|
401
|
+
def get_or_create(self, **kwargs):
|
|
402
|
+
from nautobot.ipam.models import get_default_namespace, Prefix
|
|
403
|
+
|
|
404
|
+
parent = kwargs.get("parent")
|
|
405
|
+
namespace = kwargs.pop("namespace", None)
|
|
406
|
+
host = kwargs.get("host")
|
|
407
|
+
mask_length = kwargs.get("mask_length")
|
|
408
|
+
# If `host` or `mask_length` is None skip; then there is no way of getting the closest parent;
|
|
409
|
+
if parent is None and host is not None and mask_length is not None:
|
|
410
|
+
if namespace is None:
|
|
411
|
+
namespace = get_default_namespace()
|
|
412
|
+
cidr = f"{host}/{mask_length}"
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
validate_ipv46_address(host)
|
|
416
|
+
except ValidationError as err:
|
|
417
|
+
raise ValidationError({"host": err.error_list}) from err
|
|
418
|
+
try:
|
|
419
|
+
netaddr.IPNetwork(cidr)
|
|
420
|
+
except netaddr.AddrFormatError as err:
|
|
421
|
+
raise ValidationError(f"{cidr} does not appear to be an IPv4 or IPv6 network.") from err
|
|
422
|
+
parent = Prefix.objects.filter(namespace=namespace).get_closest_parent(cidr=cidr, include_self=True)
|
|
423
|
+
kwargs["parent"] = parent
|
|
424
|
+
return super().get_or_create(**kwargs)
|
|
425
|
+
|
|
400
426
|
def string_search(self, search):
|
|
401
427
|
"""
|
|
402
428
|
Interpret a search string and return useful results.
|
nautobot/ipam/tables.py
CHANGED
|
@@ -355,6 +355,9 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
355
355
|
location_count = LinkedCountColumn(
|
|
356
356
|
viewname="dcim:location_list", url_params={"prefixes": "pk"}, verbose_name="Locations"
|
|
357
357
|
)
|
|
358
|
+
cloud_networks_count = LinkedCountColumn(
|
|
359
|
+
viewname="cloud:cloudnetwork_list", url_params={"prefixes": "pk"}, verbose_name="Cloud Networks"
|
|
360
|
+
)
|
|
358
361
|
|
|
359
362
|
class Meta(BaseTable.Meta):
|
|
360
363
|
model = Prefix
|
|
@@ -368,6 +371,7 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
368
371
|
"namespace",
|
|
369
372
|
"tenant",
|
|
370
373
|
"location_count",
|
|
374
|
+
"cloud_networks_count",
|
|
371
375
|
"vlan",
|
|
372
376
|
"role",
|
|
373
377
|
"rir",
|