nautobot 2.3.0b1__py3-none-any.whl → 2.3.2__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/celery/schedulers.py +18 -0
- 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 +19 -5
- nautobot/core/tables.py +4 -1
- nautobot/core/templates/generic/object_retrieve.html +6 -6
- nautobot/core/templates/home.html +4 -3
- 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/templatetags/buttons.py +1 -1
- 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 +11 -9
- nautobot/dcim/factory.py +7 -4
- 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 +24 -10
- nautobot/dcim/tables/devicetypes.py +1 -1
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device.html +15 -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 +15 -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/utils.py +9 -6
- nautobot/dcim/views.py +4 -1
- nautobot/extras/api/serializers.py +2 -1
- nautobot/extras/api/views.py +7 -59
- nautobot/extras/factory.py +50 -12
- nautobot/extras/filters/__init__.py +18 -3
- nautobot/extras/forms/base.py +10 -4
- nautobot/extras/forms/forms.py +7 -0
- nautobot/extras/forms/mixins.py +2 -2
- nautobot/extras/homepage.py +12 -2
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/management/__init__.py +3 -0
- nautobot/extras/migrations/0111_metadata.py +4 -4
- nautobot/extras/migrations/0114_computedfield_grouping.py +17 -0
- nautobot/extras/migrations/0115_scheduledjob_time_zone.py +23 -0
- nautobot/extras/models/customfields.py +54 -0
- nautobot/extras/models/jobs.py +105 -9
- nautobot/extras/models/metadata.py +18 -18
- nautobot/extras/models/models.py +2 -0
- nautobot/extras/signals.py +14 -1
- nautobot/extras/tables.py +77 -18
- nautobot/extras/templates/extras/computedfield.html +4 -0
- nautobot/extras/templates/extras/job_detail.html +11 -0
- nautobot/extras/templates/extras/scheduledjob.html +13 -2
- nautobot/extras/tests/test_api.py +33 -27
- nautobot/extras/tests/test_filters.py +57 -1
- nautobot/extras/tests/test_jobs.py +2 -2
- nautobot/extras/tests/test_models.py +319 -19
- nautobot/extras/tests/test_views.py +26 -5
- nautobot/extras/utils.py +35 -6
- nautobot/extras/views.py +35 -51
- nautobot/ipam/api/views.py +9 -2
- nautobot/ipam/choices.py +17 -0
- nautobot/ipam/factory.py +6 -0
- nautobot/ipam/filters.py +2 -2
- nautobot/ipam/forms.py +6 -4
- nautobot/ipam/migrations/0048_vrf_status.py +23 -0
- nautobot/ipam/migrations/0049_vrf_data_migration.py +25 -0
- nautobot/ipam/models.py +11 -20
- nautobot/ipam/querysets.py +26 -0
- nautobot/ipam/tables.py +7 -2
- nautobot/ipam/templates/ipam/vrf.html +4 -0
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -0
- nautobot/ipam/tests/test_api.py +33 -3
- nautobot/ipam/tests/test_models.py +89 -2
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/ipam/views.py +10 -15
- nautobot/project-static/css/base.css +7 -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 +775 -91
- 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 +55 -23
- 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/project-static/js/homepage_layout.js +3 -0
- 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.2.dist-info}/METADATA +3 -3
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/RECORD +397 -393
- 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.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/NOTICE +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/WHEEL +0 -0
- {nautobot-2.3.0b1.dist-info → nautobot-2.3.2.dist-info}/entry_points.txt +0 -0
|
@@ -14,8 +14,15 @@ from django.db.models import ProtectedError
|
|
|
14
14
|
from django.db.utils import IntegrityError
|
|
15
15
|
from django.test import override_settings
|
|
16
16
|
from django.test.utils import isolate_apps
|
|
17
|
-
from django.utils.timezone import now
|
|
17
|
+
from django.utils.timezone import get_default_timezone, now
|
|
18
|
+
from django_celery_beat.tzcrontab import TzAwareCrontab
|
|
18
19
|
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
|
20
|
+
import time_machine
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from zoneinfo import ZoneInfo
|
|
24
|
+
except ImportError: # python 3.8
|
|
25
|
+
from backports.zoneinfo import ZoneInfo
|
|
19
26
|
|
|
20
27
|
from nautobot.circuits.models import CircuitType
|
|
21
28
|
from nautobot.core.choices import ColorChoices
|
|
@@ -30,6 +37,7 @@ from nautobot.dcim.models import (
|
|
|
30
37
|
Platform,
|
|
31
38
|
)
|
|
32
39
|
from nautobot.extras.choices import (
|
|
40
|
+
JobExecutionType,
|
|
33
41
|
JobResultStatusChoices,
|
|
34
42
|
LogLevelChoices,
|
|
35
43
|
MetadataTypeDataTypeChoices,
|
|
@@ -65,6 +73,7 @@ from nautobot.extras.models import (
|
|
|
65
73
|
ObjectMetadata,
|
|
66
74
|
Role,
|
|
67
75
|
SavedView,
|
|
76
|
+
ScheduledJob,
|
|
68
77
|
Secret,
|
|
69
78
|
SecretsGroup,
|
|
70
79
|
SecretsGroupAssociation,
|
|
@@ -1352,7 +1361,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1352
1361
|
value="Invalid assigned object type",
|
|
1353
1362
|
scoped_fields=["status"],
|
|
1354
1363
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1355
|
-
assigned_object_id=Contact.objects.first().pk,
|
|
1364
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1356
1365
|
)
|
|
1357
1366
|
obj_metadata.validated_save()
|
|
1358
1367
|
|
|
@@ -1362,29 +1371,29 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1362
1371
|
)
|
|
1363
1372
|
type_contact_team.content_types.add(ContentType.objects.get_for_model(Contact))
|
|
1364
1373
|
type_contact_team.content_types.add(ContentType.objects.get_for_model(Team))
|
|
1365
|
-
instance1 = ObjectMetadata
|
|
1374
|
+
instance1 = ObjectMetadata(
|
|
1366
1375
|
metadata_type=type_contact_team,
|
|
1367
1376
|
contact=Contact.objects.first(),
|
|
1368
1377
|
team=Team.objects.first(),
|
|
1369
1378
|
scoped_fields=["address"],
|
|
1370
1379
|
assigned_object_type=ContentType.objects.get_for_model(Contact),
|
|
1371
|
-
assigned_object_id=Contact.objects.first().pk,
|
|
1380
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1372
1381
|
)
|
|
1373
|
-
instance2 = ObjectMetadata
|
|
1382
|
+
instance2 = ObjectMetadata(
|
|
1374
1383
|
metadata_type=type_contact_team,
|
|
1375
1384
|
contact=None,
|
|
1376
1385
|
team=None,
|
|
1377
1386
|
scoped_fields=["phone"],
|
|
1378
1387
|
assigned_object_type=ContentType.objects.get_for_model(Contact),
|
|
1379
|
-
assigned_object_id=Contact.objects.last().pk,
|
|
1388
|
+
assigned_object_id=Contact.objects.filter(associated_object_metadata__isnull=True).last().pk,
|
|
1380
1389
|
)
|
|
1381
|
-
instance3 = ObjectMetadata
|
|
1390
|
+
instance3 = ObjectMetadata(
|
|
1382
1391
|
metadata_type=type_contact_team,
|
|
1383
1392
|
contact=Contact.objects.first(),
|
|
1384
1393
|
team=None,
|
|
1385
1394
|
scoped_fields=["email"],
|
|
1386
1395
|
assigned_object_type=ContentType.objects.get_for_model(Team),
|
|
1387
|
-
assigned_object_id=Team.objects.first().pk,
|
|
1396
|
+
assigned_object_id=Team.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1388
1397
|
)
|
|
1389
1398
|
with self.assertRaises(ValidationError):
|
|
1390
1399
|
instance1.validated_save()
|
|
@@ -1407,7 +1416,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1407
1416
|
value="Some text value",
|
|
1408
1417
|
scoped_fields=["status", "parent"],
|
|
1409
1418
|
assigned_object_type=obj_type,
|
|
1410
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1419
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1411
1420
|
)
|
|
1412
1421
|
obj_metadata.save()
|
|
1413
1422
|
|
|
@@ -1440,7 +1449,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1440
1449
|
value=15,
|
|
1441
1450
|
scoped_fields=["status", "parent"],
|
|
1442
1451
|
assigned_object_type=obj_type,
|
|
1443
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1452
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1444
1453
|
)
|
|
1445
1454
|
obj_metadata.validated_save()
|
|
1446
1455
|
|
|
@@ -1484,7 +1493,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1484
1493
|
value=15.245,
|
|
1485
1494
|
scoped_fields=["status", "parent"],
|
|
1486
1495
|
assigned_object_type=obj_type,
|
|
1487
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1496
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1488
1497
|
)
|
|
1489
1498
|
obj_metadata.validated_save()
|
|
1490
1499
|
|
|
@@ -1525,7 +1534,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1525
1534
|
value=False,
|
|
1526
1535
|
scoped_fields=["status", "parent"],
|
|
1527
1536
|
assigned_object_type=obj_type,
|
|
1528
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1537
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1529
1538
|
)
|
|
1530
1539
|
obj_metadata.validated_save()
|
|
1531
1540
|
|
|
@@ -1559,7 +1568,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1559
1568
|
value="1994-01-01",
|
|
1560
1569
|
scoped_fields=["status", "parent"],
|
|
1561
1570
|
assigned_object_type=obj_type,
|
|
1562
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1571
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1563
1572
|
)
|
|
1564
1573
|
obj_metadata.validated_save()
|
|
1565
1574
|
|
|
@@ -1596,7 +1605,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1596
1605
|
value="2024-06-27T17:58:47-0500",
|
|
1597
1606
|
scoped_fields=["status", "parent"],
|
|
1598
1607
|
assigned_object_type=obj_type,
|
|
1599
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1608
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1600
1609
|
)
|
|
1601
1610
|
obj_metadata.validated_save()
|
|
1602
1611
|
|
|
@@ -1663,7 +1672,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1663
1672
|
value="Option A",
|
|
1664
1673
|
scoped_fields=["status", "parent"],
|
|
1665
1674
|
assigned_object_type=obj_type,
|
|
1666
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1675
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1667
1676
|
)
|
|
1668
1677
|
obj_metadata.validated_save()
|
|
1669
1678
|
|
|
@@ -1689,7 +1698,7 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1689
1698
|
value=["Option A"],
|
|
1690
1699
|
scoped_fields=["status", "parent"],
|
|
1691
1700
|
assigned_object_type=obj_type,
|
|
1692
|
-
assigned_object_id=Location.objects.first().pk,
|
|
1701
|
+
assigned_object_id=Location.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1693
1702
|
)
|
|
1694
1703
|
obj_metadata.validated_save()
|
|
1695
1704
|
|
|
@@ -1700,20 +1709,22 @@ class ObjectMetadataTest(ModelTestCases.BaseModelTestCase):
|
|
|
1700
1709
|
self.assertIn(f"Invalid choice(s) ({invalid_options})", str(context.exception))
|
|
1701
1710
|
|
|
1702
1711
|
def test_no_scoped_fields_overlap(self):
|
|
1703
|
-
"""
|
|
1712
|
+
"""
|
|
1713
|
+
Test that overlapping scoped_fields of ObjectMetadata with same metadata_type/assigned_object is not allowed.
|
|
1714
|
+
"""
|
|
1704
1715
|
ObjectMetadata.objects.create(
|
|
1705
1716
|
metadata_type=MetadataType.objects.first(),
|
|
1706
1717
|
contact=Contact.objects.first(),
|
|
1707
1718
|
scoped_fields=["host", "mask_length", "type", "role", "status"],
|
|
1708
1719
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1709
|
-
assigned_object_id=IPAddress.objects.first().pk,
|
|
1720
|
+
assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1710
1721
|
)
|
|
1711
1722
|
instance2 = ObjectMetadata.objects.create(
|
|
1712
1723
|
metadata_type=MetadataType.objects.first(),
|
|
1713
1724
|
contact=Contact.objects.first(),
|
|
1714
1725
|
scoped_fields=[],
|
|
1715
1726
|
assigned_object_type=ContentType.objects.get_for_model(IPAddress),
|
|
1716
|
-
assigned_object_id=IPAddress.objects.first().pk,
|
|
1727
|
+
assigned_object_id=IPAddress.objects.filter(associated_object_metadata__isnull=True).first().pk,
|
|
1717
1728
|
)
|
|
1718
1729
|
with self.assertRaises(ValidationError):
|
|
1719
1730
|
# try scope all fields
|
|
@@ -1788,6 +1799,295 @@ class SavedViewTest(ModelTestCases.BaseModelTestCase):
|
|
|
1788
1799
|
self.assertEqual(self.ipaddress_global_sv.is_shared, True)
|
|
1789
1800
|
|
|
1790
1801
|
|
|
1802
|
+
@override_settings(TIME_ZONE="UTC")
|
|
1803
|
+
class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
1804
|
+
"""Tests for the `ScheduledJob` model class."""
|
|
1805
|
+
|
|
1806
|
+
model = ScheduledJob
|
|
1807
|
+
|
|
1808
|
+
def setUp(self):
|
|
1809
|
+
self.user = User.objects.create_user(username="scheduledjobuser")
|
|
1810
|
+
self.job_model = JobModel.objects.get(name="TestPass")
|
|
1811
|
+
|
|
1812
|
+
self.daily_utc_job = ScheduledJob.objects.create(
|
|
1813
|
+
name="Daily UTC Job",
|
|
1814
|
+
task="pass.TestPass",
|
|
1815
|
+
job_model=self.job_model,
|
|
1816
|
+
interval=JobExecutionType.TYPE_DAILY,
|
|
1817
|
+
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=get_default_timezone()),
|
|
1818
|
+
time_zone=get_default_timezone(),
|
|
1819
|
+
)
|
|
1820
|
+
self.daily_est_job = ScheduledJob.objects.create(
|
|
1821
|
+
name="Daily EST Job",
|
|
1822
|
+
task="pass.TestPass",
|
|
1823
|
+
job_model=self.job_model,
|
|
1824
|
+
interval=JobExecutionType.TYPE_DAILY,
|
|
1825
|
+
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
1826
|
+
time_zone=ZoneInfo("America/New_York"),
|
|
1827
|
+
)
|
|
1828
|
+
self.crontab_utc_job = ScheduledJob.create_schedule(
|
|
1829
|
+
job_model=self.job_model,
|
|
1830
|
+
user=self.user,
|
|
1831
|
+
name="Crontab UTC Job",
|
|
1832
|
+
interval=JobExecutionType.TYPE_CUSTOM,
|
|
1833
|
+
crontab="0 17 * * *",
|
|
1834
|
+
)
|
|
1835
|
+
self.crontab_est_job = ScheduledJob.objects.create(
|
|
1836
|
+
name="Crontab EST Job",
|
|
1837
|
+
task="pass.TestPass",
|
|
1838
|
+
job_model=self.job_model,
|
|
1839
|
+
interval=JobExecutionType.TYPE_CUSTOM,
|
|
1840
|
+
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
1841
|
+
time_zone=ZoneInfo("America/New_York"),
|
|
1842
|
+
crontab="0 17 * * *",
|
|
1843
|
+
)
|
|
1844
|
+
self.one_off_utc_job = ScheduledJob.objects.create(
|
|
1845
|
+
name="One-off UTC Job",
|
|
1846
|
+
task="pass.TestPass",
|
|
1847
|
+
job_model=self.job_model,
|
|
1848
|
+
interval=JobExecutionType.TYPE_FUTURE,
|
|
1849
|
+
start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("UTC")),
|
|
1850
|
+
time_zone=ZoneInfo("UTC"),
|
|
1851
|
+
)
|
|
1852
|
+
self.one_off_est_job = ScheduledJob.create_schedule(
|
|
1853
|
+
job_model=self.job_model,
|
|
1854
|
+
user=self.user,
|
|
1855
|
+
name="One-off EST Job",
|
|
1856
|
+
interval=JobExecutionType.TYPE_FUTURE,
|
|
1857
|
+
start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
1858
|
+
)
|
|
1859
|
+
|
|
1860
|
+
def test_schedule(self):
|
|
1861
|
+
"""Test the schedule property."""
|
|
1862
|
+
with self.subTest("Test TYPE_DAILY schedules"):
|
|
1863
|
+
daily_utc_schedule = self.daily_utc_job.schedule
|
|
1864
|
+
daily_est_schedule = self.daily_est_job.schedule
|
|
1865
|
+
self.assertIsInstance(daily_utc_schedule, TzAwareCrontab)
|
|
1866
|
+
self.assertIsInstance(daily_est_schedule, TzAwareCrontab)
|
|
1867
|
+
self.assertNotEqual(daily_utc_schedule, daily_est_schedule)
|
|
1868
|
+
# Crontabs are validated in test_to_cron()
|
|
1869
|
+
|
|
1870
|
+
with self.subTest("Test TYPE_CUSTOM schedules"):
|
|
1871
|
+
crontab_utc_schedule = self.crontab_utc_job.schedule
|
|
1872
|
+
crontab_est_schedule = self.crontab_est_job.schedule
|
|
1873
|
+
self.assertIsInstance(crontab_utc_schedule, TzAwareCrontab)
|
|
1874
|
+
self.assertIsInstance(crontab_est_schedule, TzAwareCrontab)
|
|
1875
|
+
self.assertNotEqual(crontab_utc_schedule, crontab_est_schedule)
|
|
1876
|
+
# Crontabs are validated in test_to_cron()
|
|
1877
|
+
|
|
1878
|
+
with self.subTest("Test TYPE_FUTURE schedules"):
|
|
1879
|
+
# TYPE_FUTURE schedules are one off, not cron tabs:
|
|
1880
|
+
self.assertEqual(self.one_off_utc_job.schedule.clocked_time, self.one_off_utc_job.start_time)
|
|
1881
|
+
self.assertEqual(self.one_off_est_job.schedule.clocked_time, self.one_off_est_job.start_time)
|
|
1882
|
+
self.assertEqual(
|
|
1883
|
+
self.one_off_est_job.schedule.clocked_time - self.one_off_utc_job.schedule.clocked_time,
|
|
1884
|
+
timedelta(hours=5),
|
|
1885
|
+
)
|
|
1886
|
+
|
|
1887
|
+
def test_to_cron(self):
|
|
1888
|
+
"""Test the to_cron() method and its interaction with time zone variants."""
|
|
1889
|
+
|
|
1890
|
+
with self.subTest("Test TYPE_DAILY schedule with UTC time zone and UTC schedule time zone"):
|
|
1891
|
+
self.daily_utc_job.refresh_from_db()
|
|
1892
|
+
daily_utc_schedule = self.daily_utc_job.to_cron()
|
|
1893
|
+
self.assertEqual(daily_utc_schedule.tz, ZoneInfo("UTC"))
|
|
1894
|
+
self.assertEqual(daily_utc_schedule.hour, {17})
|
|
1895
|
+
self.assertEqual(daily_utc_schedule.minute, {0})
|
|
1896
|
+
last_run = datetime(2050, 1, 21, 17, 0, tzinfo=ZoneInfo("UTC"))
|
|
1897
|
+
with time_machine.travel("2050-01-22 16:59 +0000"):
|
|
1898
|
+
is_due, _ = daily_utc_schedule.is_due(last_run_at=last_run)
|
|
1899
|
+
self.assertFalse(is_due)
|
|
1900
|
+
with time_machine.travel("2050-01-22 17:00 +0000"):
|
|
1901
|
+
is_due, _ = daily_utc_schedule.is_due(last_run_at=last_run)
|
|
1902
|
+
self.assertTrue(is_due)
|
|
1903
|
+
|
|
1904
|
+
with self.subTest("Test TYPE_DAILY schedule with UTC time zone and EST schedule time zone"):
|
|
1905
|
+
self.daily_est_job.refresh_from_db()
|
|
1906
|
+
daily_est_schedule = self.daily_est_job.to_cron()
|
|
1907
|
+
self.assertEqual(daily_est_schedule.tz, ZoneInfo("America/New_York"))
|
|
1908
|
+
self.assertEqual(daily_est_schedule.hour, {17})
|
|
1909
|
+
self.assertEqual(daily_est_schedule.minute, {0})
|
|
1910
|
+
last_run = datetime(2050, 1, 21, 22, 0, tzinfo=ZoneInfo("UTC"))
|
|
1911
|
+
with time_machine.travel("2050-01-22 21:59 +0000"):
|
|
1912
|
+
is_due, _ = daily_est_schedule.is_due(last_run_at=last_run)
|
|
1913
|
+
self.assertFalse(is_due)
|
|
1914
|
+
with time_machine.travel("2050-01-22 22:00 +0000"):
|
|
1915
|
+
is_due, _ = daily_est_schedule.is_due(last_run_at=last_run)
|
|
1916
|
+
self.assertTrue(is_due)
|
|
1917
|
+
|
|
1918
|
+
with self.subTest("Test TYPE_CUSTOM schedule with UTC time zone and UTC schedule time zone"):
|
|
1919
|
+
self.crontab_utc_job.refresh_from_db()
|
|
1920
|
+
crontab_utc_schedule = self.crontab_utc_job.to_cron()
|
|
1921
|
+
self.assertEqual(crontab_utc_schedule.tz, ZoneInfo("UTC"))
|
|
1922
|
+
self.assertEqual(crontab_utc_schedule.hour, {17})
|
|
1923
|
+
self.assertEqual(crontab_utc_schedule.minute, {0})
|
|
1924
|
+
|
|
1925
|
+
with self.subTest("Test TYPE_CUSTOM schedule with UTC time zone and EST schedule time zone"):
|
|
1926
|
+
self.crontab_est_job.refresh_from_db()
|
|
1927
|
+
crontab_est_schedule = self.crontab_est_job.to_cron()
|
|
1928
|
+
self.assertEqual(crontab_est_schedule.tz, ZoneInfo("America/New_York"))
|
|
1929
|
+
self.assertEqual(crontab_est_schedule.hour, {17})
|
|
1930
|
+
self.assertEqual(crontab_est_schedule.minute, {0})
|
|
1931
|
+
|
|
1932
|
+
with self.subTest("Test TYPE_FUTURE schedules do not map to cron"):
|
|
1933
|
+
with self.assertRaises(ValueError):
|
|
1934
|
+
self.one_off_utc_job.to_cron()
|
|
1935
|
+
with self.assertRaises(ValueError):
|
|
1936
|
+
self.one_off_est_job.to_cron()
|
|
1937
|
+
|
|
1938
|
+
with override_settings(TIME_ZONE="America/New_York"):
|
|
1939
|
+
with self.subTest("Test TYPE_DAILY schedule with EST time zone and UTC schedule time zone"):
|
|
1940
|
+
self.daily_utc_job.refresh_from_db()
|
|
1941
|
+
daily_utc_schedule = self.daily_utc_job.to_cron()
|
|
1942
|
+
self.assertEqual(daily_utc_schedule.tz, ZoneInfo("UTC"))
|
|
1943
|
+
self.assertEqual(daily_utc_schedule.hour, {17})
|
|
1944
|
+
self.assertEqual(daily_utc_schedule.minute, {0})
|
|
1945
|
+
last_run = datetime(2050, 1, 21, 12, 0, tzinfo=ZoneInfo("America/New_York"))
|
|
1946
|
+
with time_machine.travel("2050-01-22 11:59 -0500"):
|
|
1947
|
+
is_due, _ = daily_utc_schedule.is_due(last_run_at=last_run)
|
|
1948
|
+
self.assertFalse(is_due)
|
|
1949
|
+
with time_machine.travel("2050-01-22 12:00 -0500"):
|
|
1950
|
+
is_due, _ = daily_utc_schedule.is_due(last_run_at=last_run)
|
|
1951
|
+
self.assertTrue(is_due)
|
|
1952
|
+
|
|
1953
|
+
with self.subTest("Test TYPE_DAILY schedule with EST time zone and EST schedule time zone"):
|
|
1954
|
+
self.daily_est_job.refresh_from_db()
|
|
1955
|
+
daily_est_schedule = self.daily_est_job.to_cron()
|
|
1956
|
+
self.assertEqual(daily_est_schedule.tz, ZoneInfo("America/New_York"))
|
|
1957
|
+
self.assertEqual(daily_est_schedule.hour, {17})
|
|
1958
|
+
self.assertEqual(daily_est_schedule.minute, {0})
|
|
1959
|
+
last_run = datetime(2050, 1, 21, 22, 0, tzinfo=ZoneInfo("America/New_York"))
|
|
1960
|
+
with time_machine.travel("2050-01-22 16:59 -0500"):
|
|
1961
|
+
is_due, _ = daily_est_schedule.is_due(last_run_at=last_run)
|
|
1962
|
+
self.assertFalse(is_due)
|
|
1963
|
+
with time_machine.travel("2050-01-22 17:00 -0500"):
|
|
1964
|
+
is_due, _ = daily_est_schedule.is_due(last_run_at=last_run)
|
|
1965
|
+
self.assertTrue(is_due)
|
|
1966
|
+
|
|
1967
|
+
with self.subTest("Test TYPE_CUSTOM schedule with EST time zone and UTC schedule time zone"):
|
|
1968
|
+
self.crontab_utc_job.refresh_from_db()
|
|
1969
|
+
crontab_utc_schedule = self.crontab_utc_job.to_cron()
|
|
1970
|
+
self.assertEqual(crontab_utc_schedule.tz, ZoneInfo("UTC"))
|
|
1971
|
+
self.assertEqual(crontab_utc_schedule.hour, {17})
|
|
1972
|
+
self.assertEqual(crontab_utc_schedule.minute, {0})
|
|
1973
|
+
|
|
1974
|
+
with self.subTest("Test TYPE_CUSTOM schedule with EST time zone and EST schedule time zone"):
|
|
1975
|
+
self.crontab_est_job.refresh_from_db()
|
|
1976
|
+
crontab_est_schedule = self.crontab_est_job.to_cron()
|
|
1977
|
+
self.assertEqual(crontab_est_schedule.tz, ZoneInfo("America/New_York"))
|
|
1978
|
+
self.assertEqual(crontab_est_schedule.hour, {17})
|
|
1979
|
+
self.assertEqual(crontab_est_schedule.minute, {0})
|
|
1980
|
+
|
|
1981
|
+
def test_crontab_dst(self):
|
|
1982
|
+
"""Test that TYPE_CUSTOM behavior around DST is as expected."""
|
|
1983
|
+
cronjob = ScheduledJob.objects.create(
|
|
1984
|
+
name="DST Aware Cronjob",
|
|
1985
|
+
task="pass.TestPass",
|
|
1986
|
+
job_model=self.job_model,
|
|
1987
|
+
enabled=False,
|
|
1988
|
+
interval=JobExecutionType.TYPE_CUSTOM,
|
|
1989
|
+
start_time=datetime(year=2024, month=1, day=1, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
1990
|
+
crontab="0 17 * * *", # 5 PM local time
|
|
1991
|
+
time_zone=ZoneInfo("America/New_York"),
|
|
1992
|
+
)
|
|
1993
|
+
|
|
1994
|
+
# Before DST takes effect
|
|
1995
|
+
with self.subTest("Test UTC time zone with EST job"):
|
|
1996
|
+
cronjob.refresh_from_db()
|
|
1997
|
+
crontab = cronjob.to_cron()
|
|
1998
|
+
with time_machine.travel("2024-03-09 21:59 +0000"):
|
|
1999
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2000
|
+
self.assertFalse(is_due)
|
|
2001
|
+
with time_machine.travel("2024-03-09 22:00 +0000"):
|
|
2002
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2003
|
+
self.assertTrue(is_due)
|
|
2004
|
+
|
|
2005
|
+
with self.subTest("Test EST time zone with EST job"), override_settings(TIME_ZONE="America/New_York"):
|
|
2006
|
+
cronjob.refresh_from_db()
|
|
2007
|
+
crontab = cronjob.to_cron()
|
|
2008
|
+
with time_machine.travel("2024-03-09 16:59 -0500"):
|
|
2009
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2010
|
+
self.assertFalse(is_due)
|
|
2011
|
+
with time_machine.travel("2024-03-09 17:00 -0500"):
|
|
2012
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2013
|
+
self.assertTrue(is_due)
|
|
2014
|
+
|
|
2015
|
+
# Day that DST takes effect
|
|
2016
|
+
with self.subTest("Test UTC time zone with EDT job"):
|
|
2017
|
+
cronjob.refresh_from_db()
|
|
2018
|
+
crontab = cronjob.to_cron()
|
|
2019
|
+
with time_machine.travel("2024-03-10 20:59 +0000"):
|
|
2020
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2021
|
+
self.assertFalse(is_due)
|
|
2022
|
+
with time_machine.travel("2024-03-10 21:00 +0000"):
|
|
2023
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2024
|
+
self.assertTrue(is_due)
|
|
2025
|
+
|
|
2026
|
+
with self.subTest("Test EDT time zone with EDT job"), override_settings(TIME_ZONE="America/New_York"):
|
|
2027
|
+
cronjob.refresh_from_db()
|
|
2028
|
+
crontab = cronjob.to_cron()
|
|
2029
|
+
with time_machine.travel("2024-03-10 16:59 -0400"):
|
|
2030
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2031
|
+
self.assertFalse(is_due)
|
|
2032
|
+
with time_machine.travel("2024-03-10 17:00 -0400"):
|
|
2033
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2034
|
+
self.assertTrue(is_due)
|
|
2035
|
+
|
|
2036
|
+
def test_daily_dst(self):
|
|
2037
|
+
"""Test the interaction of TYPE_DAILY around DST."""
|
|
2038
|
+
daily = ScheduledJob.objects.create(
|
|
2039
|
+
name="Daily Job",
|
|
2040
|
+
task="pass.TestPass",
|
|
2041
|
+
job_model=self.job_model,
|
|
2042
|
+
enabled=False,
|
|
2043
|
+
interval=JobExecutionType.TYPE_DAILY,
|
|
2044
|
+
start_time=datetime(year=2024, month=1, day=1, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
2045
|
+
time_zone=ZoneInfo("America/New_York"),
|
|
2046
|
+
)
|
|
2047
|
+
|
|
2048
|
+
# Before DST takes effect
|
|
2049
|
+
with self.subTest("Test UTC time zone with EST job"):
|
|
2050
|
+
daily.refresh_from_db()
|
|
2051
|
+
crontab = daily.to_cron()
|
|
2052
|
+
with time_machine.travel("2024-03-09 21:59 +0000"):
|
|
2053
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2054
|
+
self.assertFalse(is_due)
|
|
2055
|
+
with time_machine.travel("2024-03-09 22:00 +0000"):
|
|
2056
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2057
|
+
self.assertTrue(is_due)
|
|
2058
|
+
|
|
2059
|
+
with self.subTest("Test EST time zone with EST job"), override_settings(TIME_ZONE="America/New_York"):
|
|
2060
|
+
daily.refresh_from_db()
|
|
2061
|
+
crontab = daily.to_cron()
|
|
2062
|
+
with time_machine.travel("2024-03-09 16:59 -0500"):
|
|
2063
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2064
|
+
self.assertFalse(is_due)
|
|
2065
|
+
with time_machine.travel("2024-03-09 17:00 -0500"):
|
|
2066
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 8, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2067
|
+
self.assertTrue(is_due)
|
|
2068
|
+
|
|
2069
|
+
# Day that DST takes effect
|
|
2070
|
+
with self.subTest("Test UTC time zone with EDT job"):
|
|
2071
|
+
daily.refresh_from_db()
|
|
2072
|
+
crontab = daily.to_cron()
|
|
2073
|
+
with time_machine.travel("2024-03-10 20:59 +0000"):
|
|
2074
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2075
|
+
self.assertFalse(is_due)
|
|
2076
|
+
with time_machine.travel("2024-03-10 21:00 +0000"):
|
|
2077
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2078
|
+
self.assertTrue(is_due)
|
|
2079
|
+
|
|
2080
|
+
with self.subTest("Test EDT time zone with EDT job"), override_settings(TIME_ZONE="America/New_York"):
|
|
2081
|
+
daily.refresh_from_db()
|
|
2082
|
+
crontab = daily.to_cron()
|
|
2083
|
+
with time_machine.travel("2024-03-10 16:59 -0400"):
|
|
2084
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2085
|
+
self.assertFalse(is_due)
|
|
2086
|
+
with time_machine.travel("2024-03-10 17:00 -0400"):
|
|
2087
|
+
is_due, _ = crontab.is_due(last_run_at=datetime(2024, 3, 9, 17, 0, tzinfo=ZoneInfo("America/New_York")))
|
|
2088
|
+
self.assertTrue(is_due)
|
|
2089
|
+
|
|
2090
|
+
|
|
1791
2091
|
class SecretTest(ModelTestCases.BaseModelTestCase):
|
|
1792
2092
|
"""
|
|
1793
2093
|
Tests for the `Secret` model class.
|
|
@@ -1744,16 +1744,17 @@ class ScheduledJobTestCase(
|
|
|
1744
1744
|
ScheduledJob.objects.create(
|
|
1745
1745
|
name="test2",
|
|
1746
1746
|
task="pass.TestPass",
|
|
1747
|
-
interval=JobExecutionType.
|
|
1747
|
+
interval=JobExecutionType.TYPE_DAILY,
|
|
1748
1748
|
user=user,
|
|
1749
1749
|
start_time=timezone.now(),
|
|
1750
1750
|
)
|
|
1751
1751
|
ScheduledJob.objects.create(
|
|
1752
1752
|
name="test3",
|
|
1753
1753
|
task="pass.TestPass",
|
|
1754
|
-
interval=JobExecutionType.
|
|
1754
|
+
interval=JobExecutionType.TYPE_CUSTOM,
|
|
1755
1755
|
user=user,
|
|
1756
1756
|
start_time=timezone.now(),
|
|
1757
|
+
crontab="15 10 * * *",
|
|
1757
1758
|
)
|
|
1758
1759
|
|
|
1759
1760
|
def test_only_enabled_is_listed(self):
|
|
@@ -2594,7 +2595,7 @@ class JobTestCase(
|
|
|
2594
2595
|
|
|
2595
2596
|
self.assertInHTML('<option value="uniquequeue" selected>', content)
|
|
2596
2597
|
self.assertInHTML(
|
|
2597
|
-
'<input type="text" name="var" value="456" class="form-control
|
|
2598
|
+
'<input type="text" name="var" value="456" class="form-control" required placeholder="None" id="id_var">',
|
|
2598
2599
|
content,
|
|
2599
2600
|
)
|
|
2600
2601
|
self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
|
|
@@ -2994,6 +2995,27 @@ class JobButtonRenderingTestCase(TestCase):
|
|
|
2994
2995
|
)
|
|
2995
2996
|
|
|
2996
2997
|
|
|
2998
|
+
class JobCustomTemplateTestCase(TestCase):
|
|
2999
|
+
@classmethod
|
|
3000
|
+
def setUpTestData(cls):
|
|
3001
|
+
# Job model objects are automatically created during database migrations
|
|
3002
|
+
|
|
3003
|
+
# But we do need to make sure the ones we're testing are flagged appropriately
|
|
3004
|
+
cls.example_job = Job.objects.get(job_class_name="ExampleCustomFormJob")
|
|
3005
|
+
cls.example_job.enabled = True
|
|
3006
|
+
cls.example_job.save()
|
|
3007
|
+
|
|
3008
|
+
cls.run_url = reverse("extras:job_run", kwargs={"pk": cls.example_job.pk})
|
|
3009
|
+
|
|
3010
|
+
def test_rendering_custom_template(self):
|
|
3011
|
+
obj_perm = ObjectPermission(name="Test permission", actions=["view", "run"])
|
|
3012
|
+
obj_perm.save()
|
|
3013
|
+
obj_perm.users.add(self.user)
|
|
3014
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
|
|
3015
|
+
with self.assertTemplateUsed("example_app/custom_job_form.html"):
|
|
3016
|
+
self.client.get(self.run_url)
|
|
3017
|
+
|
|
3018
|
+
|
|
2997
3019
|
# TODO: Convert to StandardTestCases.Views
|
|
2998
3020
|
class ObjectChangeTestCase(TestCase):
|
|
2999
3021
|
user_permissions = ("extras.view_objectchange",)
|
|
@@ -3029,8 +3051,7 @@ class ObjectChangeTestCase(TestCase):
|
|
|
3029
3051
|
|
|
3030
3052
|
|
|
3031
3053
|
class ObjectMetadataTestCase(
|
|
3032
|
-
ViewTestCases.
|
|
3033
|
-
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
3054
|
+
ViewTestCases.GetObjectViewTestCase,
|
|
3034
3055
|
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
3035
3056
|
ViewTestCases.ListObjectsViewTestCase,
|
|
3036
3057
|
):
|
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
|