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
|
@@ -72,7 +72,7 @@ class Migration(migrations.Migration):
|
|
|
72
72
|
("last_updated", models.DateTimeField(auto_now=True, null=True)),
|
|
73
73
|
(
|
|
74
74
|
"scoped_fields",
|
|
75
|
-
nautobot.core.models.fields.JSONArrayField(base_field=models.CharField(max_length=255)),
|
|
75
|
+
nautobot.core.models.fields.JSONArrayField(base_field=models.CharField(max_length=255), blank=True),
|
|
76
76
|
),
|
|
77
77
|
("_value", models.JSONField(blank=True, null=True)),
|
|
78
78
|
("assigned_object_id", models.UUIDField(db_index=True)),
|
|
@@ -91,7 +91,7 @@ class Migration(migrations.Migration):
|
|
|
91
91
|
blank=True,
|
|
92
92
|
null=True,
|
|
93
93
|
on_delete=django.db.models.deletion.PROTECT,
|
|
94
|
-
related_name="
|
|
94
|
+
related_name="object_metadata",
|
|
95
95
|
to="extras.contact",
|
|
96
96
|
),
|
|
97
97
|
),
|
|
@@ -99,7 +99,7 @@ class Migration(migrations.Migration):
|
|
|
99
99
|
"metadata_type",
|
|
100
100
|
models.ForeignKey(
|
|
101
101
|
on_delete=django.db.models.deletion.PROTECT,
|
|
102
|
-
related_name="
|
|
102
|
+
related_name="object_metadata",
|
|
103
103
|
to="extras.metadatatype",
|
|
104
104
|
),
|
|
105
105
|
),
|
|
@@ -109,7 +109,7 @@ class Migration(migrations.Migration):
|
|
|
109
109
|
blank=True,
|
|
110
110
|
null=True,
|
|
111
111
|
on_delete=django.db.models.deletion.PROTECT,
|
|
112
|
-
related_name="
|
|
112
|
+
related_name="object_metadata",
|
|
113
113
|
to="extras.team",
|
|
114
114
|
),
|
|
115
115
|
),
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 4.2.15 on 2024-08-09 14:28
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("extras", "0113_saved_views"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="computedfield",
|
|
14
|
+
name="grouping",
|
|
15
|
+
field=models.CharField(blank=True, max_length=255),
|
|
16
|
+
),
|
|
17
|
+
]
|
|
@@ -88,6 +88,11 @@ class ComputedField(
|
|
|
88
88
|
help_text="Internal field name. Please use underscores rather than dashes in this key.",
|
|
89
89
|
slugify_function=slugify_dashes_to_underscores,
|
|
90
90
|
)
|
|
91
|
+
grouping = models.CharField(
|
|
92
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
93
|
+
blank=True,
|
|
94
|
+
help_text="Human-readable grouping that this computed field belongs to.",
|
|
95
|
+
)
|
|
91
96
|
label = models.CharField(max_length=CHARFIELD_MAX_LENGTH, help_text="Name of the field as displayed to users")
|
|
92
97
|
description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
|
|
93
98
|
template = models.TextField(max_length=500, help_text="Jinja2 template code for field value")
|
|
@@ -295,6 +300,55 @@ class CustomFieldModel(models.Model):
|
|
|
295
300
|
return computed_field.render(context={"obj": self})
|
|
296
301
|
return computed_field.template
|
|
297
302
|
|
|
303
|
+
def get_computed_fields_grouping_basic(self):
|
|
304
|
+
"""
|
|
305
|
+
This method exists to help call get_computed_field_groupings() in templates where a function argument (advanced_ui) cannot be specified.
|
|
306
|
+
Return a dictonary of computed fields grouped by the same grouping in the form
|
|
307
|
+
{
|
|
308
|
+
<grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
|
|
309
|
+
...
|
|
310
|
+
<grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
|
|
311
|
+
...
|
|
312
|
+
}
|
|
313
|
+
which have advanced_ui set to False
|
|
314
|
+
"""
|
|
315
|
+
return self.get_computed_fields_grouping(advanced_ui=False)
|
|
316
|
+
|
|
317
|
+
def get_computed_fields_grouping_advanced(self):
|
|
318
|
+
"""
|
|
319
|
+
This method exists to help call get_computed_field_groupings() in templates where a function argument (advanced_ui) cannot be specified.
|
|
320
|
+
Return a dictonary of computed fields grouped by the same grouping in the form
|
|
321
|
+
{
|
|
322
|
+
<grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
|
|
323
|
+
...
|
|
324
|
+
<grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
|
|
325
|
+
...
|
|
326
|
+
}
|
|
327
|
+
which have advanced_ui set to True
|
|
328
|
+
"""
|
|
329
|
+
return self.get_computed_fields_grouping(advanced_ui=True)
|
|
330
|
+
|
|
331
|
+
def get_computed_fields_grouping(self, advanced_ui=None):
|
|
332
|
+
"""
|
|
333
|
+
Return a dictonary of computed fields grouped by the same grouping in the form
|
|
334
|
+
{
|
|
335
|
+
<grouping_1>: [(cf1, <value for cf1>), (cf2, <value for cf2>), ...],
|
|
336
|
+
...
|
|
337
|
+
<grouping_5>: [(cf8, <value for cf8>), (cf9, <value for cf9>), ...],
|
|
338
|
+
...
|
|
339
|
+
}
|
|
340
|
+
"""
|
|
341
|
+
record = {}
|
|
342
|
+
computed_fields = ComputedField.objects.get_for_model(self)
|
|
343
|
+
if advanced_ui is not None:
|
|
344
|
+
computed_fields = computed_fields.filter(advanced_ui=advanced_ui)
|
|
345
|
+
|
|
346
|
+
for field in computed_fields:
|
|
347
|
+
data = (field, field.render(context={"obj": self}))
|
|
348
|
+
record.setdefault(field.grouping, []).append(data)
|
|
349
|
+
record = dict(sorted(record.items()))
|
|
350
|
+
return record
|
|
351
|
+
|
|
298
352
|
def get_computed_fields(self, label_as_key=False, advanced_ui=None):
|
|
299
353
|
"""
|
|
300
354
|
Return a dictionary of all computed fields and their rendered values for this model.
|
nautobot/extras/models/jobs.py
CHANGED
|
@@ -456,6 +456,8 @@ class JobLogEntry(BaseModel):
|
|
|
456
456
|
log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
|
|
457
457
|
absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
|
|
458
458
|
|
|
459
|
+
is_metadata_associable_model = False
|
|
460
|
+
|
|
459
461
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
|
|
460
462
|
|
|
461
463
|
def __str__(self):
|
|
@@ -1075,6 +1077,87 @@ class ScheduledJob(BaseModel):
|
|
|
1075
1077
|
day_of_week=day_of_week,
|
|
1076
1078
|
)
|
|
1077
1079
|
|
|
1080
|
+
@classmethod
|
|
1081
|
+
def create_schedule(
|
|
1082
|
+
cls,
|
|
1083
|
+
job_model,
|
|
1084
|
+
user,
|
|
1085
|
+
name=None,
|
|
1086
|
+
start_time=None,
|
|
1087
|
+
interval=JobExecutionType.TYPE_IMMEDIATELY,
|
|
1088
|
+
crontab="",
|
|
1089
|
+
profile=False,
|
|
1090
|
+
approval_required=False,
|
|
1091
|
+
task_queue=None,
|
|
1092
|
+
**job_kwargs,
|
|
1093
|
+
):
|
|
1094
|
+
"""
|
|
1095
|
+
Schedule a job with the specified parameters.
|
|
1096
|
+
|
|
1097
|
+
This method creates a schedule for a job to be executed at a specific time
|
|
1098
|
+
or interval. It handles immediate execution, custom start times, and
|
|
1099
|
+
crontab-based scheduling.
|
|
1100
|
+
|
|
1101
|
+
Parameters:
|
|
1102
|
+
job_model (JobModel): The job model instance.
|
|
1103
|
+
user (User): The user who is scheduling the job.
|
|
1104
|
+
name (str, optional): The name of the scheduled job. Defaults to None.
|
|
1105
|
+
start_time (datetime, optional): The start time for the job. Defaults to None.
|
|
1106
|
+
interval (JobExecutionType, optional): The interval type for the job execution.
|
|
1107
|
+
Defaults to JobExecutionType.TYPE_IMMEDIATELY.
|
|
1108
|
+
crontab (str, optional): The crontab string for the schedule. Defaults to "".
|
|
1109
|
+
profile (bool, optional): Flag indicating whether to profile the job. Defaults to False.
|
|
1110
|
+
approval_required (bool, optional): Flag indicating if approval is required. Defaults to False.
|
|
1111
|
+
task_queue (str, optional): The task queue for the job. Defaults to None, which will use the configured default celery queue.
|
|
1112
|
+
**job_kwargs: Additional keyword arguments to pass to the job.
|
|
1113
|
+
|
|
1114
|
+
Returns:
|
|
1115
|
+
ScheduledJob instance
|
|
1116
|
+
"""
|
|
1117
|
+
|
|
1118
|
+
if interval == JobExecutionType.TYPE_IMMEDIATELY:
|
|
1119
|
+
start_time = timezone.now()
|
|
1120
|
+
name = name or f"{job_model.name} - {start_time}"
|
|
1121
|
+
elif interval == JobExecutionType.TYPE_CUSTOM:
|
|
1122
|
+
if start_time is None:
|
|
1123
|
+
# "start_time" is checked against models.ScheduledJob.earliest_possible_time()
|
|
1124
|
+
# which returns timezone.now() + timedelta(seconds=15)
|
|
1125
|
+
start_time = timezone.now() + timedelta(seconds=20)
|
|
1126
|
+
|
|
1127
|
+
celery_kwargs = {
|
|
1128
|
+
"nautobot_job_profile": profile,
|
|
1129
|
+
"queue": task_queue,
|
|
1130
|
+
}
|
|
1131
|
+
if job_model.soft_time_limit > 0:
|
|
1132
|
+
celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
|
|
1133
|
+
if job_model.time_limit > 0:
|
|
1134
|
+
celery_kwargs["time_limit"] = job_model.time_limit
|
|
1135
|
+
|
|
1136
|
+
# 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
|
|
1137
|
+
#
|
|
1138
|
+
# We pass in task and job_model here partly for forward/backward compatibility logic, and
|
|
1139
|
+
# part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
|
|
1140
|
+
# FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
|
|
1141
|
+
# null) you still have a bit of context on the ScheduledJob as to what it was originally
|
|
1142
|
+
# scheduled for.
|
|
1143
|
+
scheduled_job = cls(
|
|
1144
|
+
name=name,
|
|
1145
|
+
task=job_model.class_path,
|
|
1146
|
+
job_model=job_model,
|
|
1147
|
+
start_time=start_time,
|
|
1148
|
+
description=f"Nautobot job {name} scheduled by {user} for {start_time}",
|
|
1149
|
+
kwargs=job_kwargs,
|
|
1150
|
+
celery_kwargs=celery_kwargs,
|
|
1151
|
+
interval=interval,
|
|
1152
|
+
one_off=(interval == JobExecutionType.TYPE_FUTURE),
|
|
1153
|
+
user=user,
|
|
1154
|
+
approval_required=approval_required,
|
|
1155
|
+
crontab=crontab,
|
|
1156
|
+
queue=task_queue,
|
|
1157
|
+
)
|
|
1158
|
+
scheduled_job.validated_save()
|
|
1159
|
+
return scheduled_job
|
|
1160
|
+
|
|
1078
1161
|
def to_cron(self):
|
|
1079
1162
|
t = self.start_time
|
|
1080
1163
|
if self.interval == JobExecutionType.TYPE_HOURLY:
|
|
@@ -61,7 +61,7 @@ class MetadataType(PrimaryModel):
|
|
|
61
61
|
|
|
62
62
|
objects = MetadataTypeManager()
|
|
63
63
|
clone_fields = ["data_type"]
|
|
64
|
-
documentation_static_path = "docs/user-guide/platform-functionality/
|
|
64
|
+
documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
|
|
65
65
|
|
|
66
66
|
class Meta:
|
|
67
67
|
ordering = ["name"]
|
|
@@ -106,7 +106,7 @@ class MetadataChoice(ChangeLoggedModel, BaseModel):
|
|
|
106
106
|
weight = models.PositiveSmallIntegerField(default=100, help_text="Higher weights appear later in the list")
|
|
107
107
|
is_metadata_associable_model = False
|
|
108
108
|
|
|
109
|
-
documentation_static_path = "docs/user-guide/platform-functionality/
|
|
109
|
+
documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
|
|
110
110
|
|
|
111
111
|
class Meta:
|
|
112
112
|
ordering = ["metadata_type", "weight", "value"]
|
|
@@ -166,19 +166,19 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
166
166
|
metadata_type = models.ForeignKey(
|
|
167
167
|
to=MetadataType,
|
|
168
168
|
on_delete=models.PROTECT,
|
|
169
|
-
related_name="
|
|
169
|
+
related_name="object_metadata",
|
|
170
170
|
)
|
|
171
171
|
contact = models.ForeignKey(
|
|
172
172
|
to=Contact,
|
|
173
173
|
on_delete=models.PROTECT,
|
|
174
|
-
related_name="
|
|
174
|
+
related_name="object_metadata",
|
|
175
175
|
blank=True,
|
|
176
176
|
null=True,
|
|
177
177
|
)
|
|
178
178
|
team = models.ForeignKey(
|
|
179
179
|
to=Team,
|
|
180
180
|
on_delete=models.PROTECT,
|
|
181
|
-
related_name="
|
|
181
|
+
related_name="object_metadata",
|
|
182
182
|
blank=True,
|
|
183
183
|
null=True,
|
|
184
184
|
)
|
|
@@ -186,6 +186,7 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
186
186
|
base_field=models.CharField(
|
|
187
187
|
max_length=CHARFIELD_MAX_LENGTH,
|
|
188
188
|
),
|
|
189
|
+
blank=True,
|
|
189
190
|
help_text="List of scoped fields, only direct fields on the model",
|
|
190
191
|
)
|
|
191
192
|
_value = models.JSONField(
|
|
@@ -199,7 +200,7 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
199
200
|
|
|
200
201
|
objects = ObjectMetadataManager()
|
|
201
202
|
natural_key_field_names = ["pk"]
|
|
202
|
-
documentation_static_path = "docs/user-guide/platform-functionality/
|
|
203
|
+
documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
|
|
203
204
|
|
|
204
205
|
class Meta:
|
|
205
206
|
ordering = ["metadata_type"]
|
|
@@ -361,12 +362,17 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
361
362
|
raise ValidationError("Date values must be in the format YYYY-MM-DD.")
|
|
362
363
|
# Validate datetime
|
|
363
364
|
elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_DATETIME:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
365
|
+
if isinstance(value, datetime):
|
|
366
|
+
# check if datetime object has tzinfo
|
|
367
|
+
if value.tzinfo is None:
|
|
368
|
+
value = value.replace(tzinfo=timezone.utc) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
|
|
369
|
+
value = value.replace(microsecond=0).isoformat() # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
|
|
370
|
+
else:
|
|
371
|
+
acceptable_datetime_formats = [
|
|
372
|
+
"YYYY-MM-DDTHH:MM:SS",
|
|
373
|
+
"YYYY-MM-DDTHH:MM:SS(+,-)zzzz",
|
|
374
|
+
"YYYY-MM-DDTHH:MM:SS(+,-)zz:zz",
|
|
375
|
+
]
|
|
370
376
|
try:
|
|
371
377
|
datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
|
|
372
378
|
except ValueError:
|
|
@@ -380,12 +386,6 @@ class ObjectMetadata(ChangeLoggedModel, BaseModel):
|
|
|
380
386
|
)
|
|
381
387
|
except TypeError:
|
|
382
388
|
raise ValidationError("Value must be a datetime or str object.")
|
|
383
|
-
else:
|
|
384
|
-
# if value is a datetime object
|
|
385
|
-
# check if datetime object has tzinfo
|
|
386
|
-
if value.tzinfo is None:
|
|
387
|
-
value = value.replace(tzinfo=timezone.utc)
|
|
388
|
-
value = value.replace(microsecond=0).isoformat()
|
|
389
389
|
# Validate selected choice
|
|
390
390
|
elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_SELECT:
|
|
391
391
|
if value not in self.metadata_type.choices.values_list("value", flat=True):
|
nautobot/extras/models/models.py
CHANGED
|
@@ -822,6 +822,8 @@ class Note(ChangeLoggedModel, BaseModel):
|
|
|
822
822
|
note = models.TextField()
|
|
823
823
|
objects = BaseManager.from_queryset(NotesQuerySet)()
|
|
824
824
|
|
|
825
|
+
is_metadata_associable_model = False
|
|
826
|
+
|
|
825
827
|
class Meta:
|
|
826
828
|
ordering = ["created"]
|
|
827
829
|
unique_together = [["assigned_object_type", "assigned_object_id", "user_name", "created"]]
|
nautobot/extras/signals.py
CHANGED
|
@@ -13,7 +13,7 @@ from django.core.cache import cache
|
|
|
13
13
|
from django.core.exceptions import ValidationError
|
|
14
14
|
from django.core.files.storage import get_storage_class
|
|
15
15
|
from django.db import transaction
|
|
16
|
-
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete, pre_save
|
|
16
|
+
from django.db.models.signals import m2m_changed, post_delete, post_migrate, post_save, pre_delete, pre_save
|
|
17
17
|
from django.dispatch import receiver
|
|
18
18
|
from django.utils import timezone
|
|
19
19
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
|
@@ -289,6 +289,19 @@ def _handle_deleted_object(sender, instance, **kwargs):
|
|
|
289
289
|
model_deletes.labels(instance._meta.model_name).inc()
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
#
|
|
293
|
+
# Content types
|
|
294
|
+
#
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@receiver(post_migrate)
|
|
298
|
+
def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs):
|
|
299
|
+
"""Clear various content-type caches after a migration."""
|
|
300
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
301
|
+
cache.delete("nautobot.extras.utils.change_logged_models_queryset")
|
|
302
|
+
cache.delete_pattern("nautobot.extras.utils.FeatureQuery.*")
|
|
303
|
+
|
|
304
|
+
|
|
292
305
|
#
|
|
293
306
|
# Custom fields
|
|
294
307
|
#
|
nautobot/extras/tables.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
|
-
from django.utils.html import format_html
|
|
2
|
+
from django.utils.html import format_html, format_html_join
|
|
3
3
|
import django_tables2 as tables
|
|
4
4
|
from django_tables2.utils import Accessor
|
|
5
5
|
from jsonschema.exceptions import ValidationError as JSONSchemaValidationError
|
|
@@ -107,6 +107,7 @@ GITREPOSITORY_BUTTONS = """
|
|
|
107
107
|
|
|
108
108
|
JOB_BUTTONS = """
|
|
109
109
|
<a href="{% url 'extras:job' pk=record.pk %}" class="btn btn-default btn-xs" title="Details"><i class="mdi mdi-information-outline" aria-hidden="true"></i></a>
|
|
110
|
+
<a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="btn btn-default btn-xs" title="Job Results"><i class="mdi mdi-format-list-bulleted" aria-hidden="true"></i></a>
|
|
110
111
|
"""
|
|
111
112
|
|
|
112
113
|
OBJECTCHANGE_OBJECT = """
|
|
@@ -715,7 +716,10 @@ class JobTable(BaseTable):
|
|
|
715
716
|
return render_markdown(value)
|
|
716
717
|
|
|
717
718
|
def render_name(self, value):
|
|
718
|
-
return format_html(
|
|
719
|
+
return format_html(
|
|
720
|
+
'<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
|
|
721
|
+
value,
|
|
722
|
+
)
|
|
719
723
|
|
|
720
724
|
class Meta(BaseTable.Meta):
|
|
721
725
|
model = JobModel
|
|
@@ -887,7 +891,16 @@ class JobResultTable(BaseTable):
|
|
|
887
891
|
"summary",
|
|
888
892
|
"actions",
|
|
889
893
|
)
|
|
890
|
-
default_columns = (
|
|
894
|
+
default_columns = (
|
|
895
|
+
"pk",
|
|
896
|
+
"date_created",
|
|
897
|
+
"name",
|
|
898
|
+
"job_model",
|
|
899
|
+
"user",
|
|
900
|
+
"status",
|
|
901
|
+
"summary",
|
|
902
|
+
"actions",
|
|
903
|
+
)
|
|
891
904
|
|
|
892
905
|
|
|
893
906
|
class JobButtonTable(BaseTable):
|
|
@@ -957,10 +970,8 @@ class MetadataTypeTable(BaseTable):
|
|
|
957
970
|
class ObjectMetadataTable(BaseTable):
|
|
958
971
|
pk = ToggleColumn()
|
|
959
972
|
metadata_type = tables.Column(linkify=True)
|
|
960
|
-
assigned_object = tables.TemplateColumn(
|
|
961
|
-
|
|
962
|
-
ObjectMetadata,
|
|
963
|
-
buttons=("delete"),
|
|
973
|
+
assigned_object = tables.TemplateColumn(
|
|
974
|
+
template_code=ASSIGNED_OBJECT, verbose_name="Assigned object", orderable=False
|
|
964
975
|
)
|
|
965
976
|
# This is needed so that render_value method below does not skip itself
|
|
966
977
|
# when metadata_type.data_type is TYPE_CONTACT_TEAM and we need it to display either contact or team
|
|
@@ -974,7 +985,6 @@ class ObjectMetadataTable(BaseTable):
|
|
|
974
985
|
"metadata_type",
|
|
975
986
|
"scoped_fields",
|
|
976
987
|
"value",
|
|
977
|
-
"actions",
|
|
978
988
|
)
|
|
979
989
|
default_columns = (
|
|
980
990
|
"pk",
|
|
@@ -982,11 +992,12 @@ class ObjectMetadataTable(BaseTable):
|
|
|
982
992
|
"scoped_fields",
|
|
983
993
|
"value",
|
|
984
994
|
"metadata_type",
|
|
985
|
-
"actions",
|
|
986
995
|
)
|
|
987
996
|
|
|
988
|
-
def render_scoped_fields(self,
|
|
989
|
-
|
|
997
|
+
def render_scoped_fields(self, value):
|
|
998
|
+
if not value:
|
|
999
|
+
return "(all fields)"
|
|
1000
|
+
return format_html_join(", ", "<code>{}</code>", ([v] for v in sorted(value)))
|
|
990
1001
|
|
|
991
1002
|
def render_value(self, record):
|
|
992
1003
|
if record.value is not None and record.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_JSON:
|
|
@@ -1108,7 +1119,15 @@ class RelationshipAssociationTable(BaseTable):
|
|
|
1108
1119
|
|
|
1109
1120
|
class Meta(BaseTable.Meta):
|
|
1110
1121
|
model = RelationshipAssociation
|
|
1111
|
-
fields = (
|
|
1122
|
+
fields = (
|
|
1123
|
+
"pk",
|
|
1124
|
+
"relationship",
|
|
1125
|
+
"source_type",
|
|
1126
|
+
"source",
|
|
1127
|
+
"destination_type",
|
|
1128
|
+
"destination",
|
|
1129
|
+
"actions",
|
|
1130
|
+
)
|
|
1112
1131
|
default_columns = ("pk", "relationship", "source", "destination", "actions")
|
|
1113
1132
|
|
|
1114
1133
|
|
|
@@ -1224,7 +1243,15 @@ class TagTable(BaseTable):
|
|
|
1224
1243
|
|
|
1225
1244
|
class Meta(BaseTable.Meta):
|
|
1226
1245
|
model = Tag
|
|
1227
|
-
fields = (
|
|
1246
|
+
fields = (
|
|
1247
|
+
"pk",
|
|
1248
|
+
"name",
|
|
1249
|
+
"items",
|
|
1250
|
+
"color",
|
|
1251
|
+
"content_types",
|
|
1252
|
+
"description",
|
|
1253
|
+
"actions",
|
|
1254
|
+
)
|
|
1228
1255
|
|
|
1229
1256
|
|
|
1230
1257
|
class TaggedItemTable(BaseTable):
|
|
@@ -1304,7 +1331,9 @@ class WebhookTable(BaseTable):
|
|
|
1304
1331
|
class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
1305
1332
|
pk = ToggleColumn()
|
|
1306
1333
|
contact_type = tables.TemplateColumn(
|
|
1307
|
-
CONTACT_OR_TEAM_ICON,
|
|
1334
|
+
CONTACT_OR_TEAM_ICON,
|
|
1335
|
+
verbose_name="Type",
|
|
1336
|
+
attrs={"td": {"style": "width:20px;"}},
|
|
1308
1337
|
)
|
|
1309
1338
|
name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
|
|
1310
1339
|
contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team.phone", verbose_name="Phone")
|
|
@@ -115,6 +115,17 @@
|
|
|
115
115
|
<td>{{ object.enabled | render_boolean }}</td>
|
|
116
116
|
<td></td>
|
|
117
117
|
</tr>
|
|
118
|
+
<tr>
|
|
119
|
+
<td>Job Results</td>
|
|
120
|
+
<td>
|
|
121
|
+
{% if object.job_results.exists %}
|
|
122
|
+
<a href="{% url 'extras:jobresult_list' %}?job_model={{ object.name | urlencode }}">{{ object.job_results.count }}</a>
|
|
123
|
+
{% else %}
|
|
124
|
+
{{ None|placeholder }}
|
|
125
|
+
{% endif %}
|
|
126
|
+
</td>
|
|
127
|
+
<td></td>
|
|
128
|
+
</tr>
|
|
118
129
|
</table>
|
|
119
130
|
</div>
|
|
120
131
|
{% endblock content_left_page %}
|
|
@@ -15,7 +15,7 @@ from rest_framework import status
|
|
|
15
15
|
from nautobot.core.choices import ColorChoices
|
|
16
16
|
from nautobot.core.models.fields import slugify_dashes_to_underscores
|
|
17
17
|
from nautobot.core.testing import APITestCase, APIViewTestCases
|
|
18
|
-
from nautobot.core.testing.utils import disable_warnings
|
|
18
|
+
from nautobot.core.testing.utils import disable_warnings, get_deletable_objects
|
|
19
19
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
20
20
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
21
21
|
from nautobot.dcim.models import (
|
|
@@ -2741,21 +2741,21 @@ class ObjectMetadataTest(APIViewTestCases.APIViewTestCase):
|
|
|
2741
2741
|
value="Hey",
|
|
2742
2742
|
scoped_fields=["parent", "status"],
|
|
2743
2743
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
2744
|
-
assigned_object_id=IPAddress.objects.first().pk,
|
|
2744
|
+
assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
2745
2745
|
)
|
|
2746
2746
|
ObjectMetadata.objects.create(
|
|
2747
2747
|
metadata_type=mdts[0],
|
|
2748
2748
|
value="Hello",
|
|
2749
2749
|
scoped_fields=["namespace"],
|
|
2750
2750
|
assigned_object_type=ContentType.objects.get_for_model(Prefix),
|
|
2751
|
-
assigned_object_id=Prefix.objects.first().pk,
|
|
2751
|
+
assigned_object_id=Prefix.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
2752
2752
|
)
|
|
2753
2753
|
ObjectMetadata.objects.create(
|
|
2754
2754
|
metadata_type=mdts[2],
|
|
2755
2755
|
contact=Contact.objects.first(),
|
|
2756
2756
|
scoped_fields=["status"],
|
|
2757
2757
|
assigned_object_type=ContentType.objects.get_for_model(Prefix),
|
|
2758
|
-
assigned_object_id=Prefix.objects.last().pk,
|
|
2758
|
+
assigned_object_id=Prefix.objects.filter(associated_object_metadata__isnull=True).last().pk,
|
|
2759
2759
|
)
|
|
2760
2760
|
cls.create_data = [
|
|
2761
2761
|
{
|
|
@@ -2763,34 +2763,41 @@ class ObjectMetadataTest(APIViewTestCases.APIViewTestCase):
|
|
|
2763
2763
|
"scoped_fields": ["location_type"],
|
|
2764
2764
|
"value": "random words",
|
|
2765
2765
|
"assigned_object_type": "dcim.location",
|
|
2766
|
-
"assigned_object_id": Location.objects.first().pk,
|
|
2766
|
+
"assigned_object_id": Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
2767
2767
|
},
|
|
2768
2768
|
{
|
|
2769
2769
|
"metadata_type": mdts[1].pk,
|
|
2770
2770
|
"scoped_fields": ["name"],
|
|
2771
2771
|
"value": "random words",
|
|
2772
2772
|
"assigned_object_type": "dcim.location",
|
|
2773
|
-
"assigned_object_id": Location.objects.first().pk,
|
|
2773
|
+
"assigned_object_id": Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
2774
2774
|
},
|
|
2775
2775
|
{
|
|
2776
2776
|
"metadata_type": mdts[2].pk,
|
|
2777
|
-
"scoped_fields": [
|
|
2777
|
+
"scoped_fields": [],
|
|
2778
2778
|
"contact": Contact.objects.first().pk,
|
|
2779
2779
|
"assigned_object_type": "dcim.device",
|
|
2780
|
-
"assigned_object_id": Device.objects.first().pk,
|
|
2780
|
+
"assigned_object_id": Device.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
2781
2781
|
},
|
|
2782
2782
|
{
|
|
2783
2783
|
"metadata_type": mdts[2].pk,
|
|
2784
2784
|
"scoped_fields": ["interfaces"],
|
|
2785
2785
|
"team": Team.objects.first().pk,
|
|
2786
2786
|
"assigned_object_type": "dcim.device",
|
|
2787
|
-
"assigned_object_id": Device.objects.
|
|
2787
|
+
"assigned_object_id": Device.objects.filter(associated_object_metadata__isnull=True).last().pk,
|
|
2788
2788
|
},
|
|
2789
2789
|
]
|
|
2790
2790
|
cls.update_data = {
|
|
2791
2791
|
"scoped_fields": ["pk"],
|
|
2792
2792
|
}
|
|
2793
2793
|
|
|
2794
|
+
def get_deletable_object(self):
|
|
2795
|
+
# TODO: CSV round-trip doesn't work for empty scoped_fields values at present. :-(
|
|
2796
|
+
instance = get_deletable_objects(self.model, self._get_queryset().exclude(scoped_fields=[])).first()
|
|
2797
|
+
if instance is None:
|
|
2798
|
+
self.fail("Couldn't find a single deletable object with non-empty scoped_fields")
|
|
2799
|
+
return instance
|
|
2800
|
+
|
|
2794
2801
|
|
|
2795
2802
|
class NoteTest(APIViewTestCases.APIViewTestCase):
|
|
2796
2803
|
model = Note
|
|
@@ -55,10 +55,10 @@ class JobTest(TestCase):
|
|
|
55
55
|
|
|
56
56
|
self.assertInHTML(
|
|
57
57
|
"""<tr><th><label for="id_var_int">Var int:</label></th><td>
|
|
58
|
-
<input class="form-control
|
|
58
|
+
<input class="form-control" id="id_var_int" max="3600" name="var_int" placeholder="None" required type="number" value="0">
|
|
59
59
|
<br><span class="helptext">Test default of 0 Falsey</span></td></tr>
|
|
60
60
|
<tr><th><label for="id_var_int_no_default">Var int no default:</label></th><td>
|
|
61
|
-
<input class="form-control
|
|
61
|
+
<input class="form-control" id="id_var_int_no_default" max="3600" name="var_int_no_default" placeholder="None" type="number">
|
|
62
62
|
<br><span class="helptext">Test default without default</span></td></tr>""",
|
|
63
63
|
form.as_table(),
|
|
64
64
|
)
|