nautobot 2.4.0b1__py3-none-any.whl → 2.4.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/apps/__init__.py +1 -1
- nautobot/apps/api.py +8 -8
- nautobot/apps/change_logging.py +2 -2
- nautobot/apps/choices.py +4 -4
- nautobot/apps/events.py +3 -3
- nautobot/apps/factory.py +2 -2
- nautobot/apps/filters.py +1 -1
- nautobot/apps/forms.py +20 -20
- nautobot/apps/graphql.py +2 -2
- nautobot/apps/jobs.py +8 -8
- nautobot/apps/models.py +19 -19
- nautobot/apps/tables.py +1 -1
- nautobot/apps/testing.py +10 -10
- nautobot/apps/ui.py +2 -2
- nautobot/apps/utils.py +7 -7
- nautobot/apps/views.py +7 -7
- nautobot/circuits/api/serializers.py +1 -0
- nautobot/circuits/api/views.py +4 -8
- nautobot/circuits/tables.py +2 -1
- nautobot/circuits/templates/circuits/circuit_create.html +1 -7
- nautobot/circuits/views.py +3 -3
- nautobot/cloud/api/views.py +6 -10
- nautobot/cloud/models.py +1 -1
- nautobot/cloud/views.py +0 -16
- nautobot/core/api/constants.py +11 -0
- nautobot/core/api/fields.py +5 -5
- nautobot/core/api/filter_backends.py +3 -9
- nautobot/core/api/schema.py +13 -2
- nautobot/core/api/serializers.py +40 -34
- nautobot/core/api/views.py +56 -4
- nautobot/core/celery/log.py +4 -4
- nautobot/core/celery/schedulers.py +2 -2
- nautobot/core/choices.py +2 -2
- nautobot/core/events/__init__.py +3 -3
- nautobot/core/filters.py +67 -35
- nautobot/core/forms/__init__.py +19 -19
- nautobot/core/forms/fields.py +14 -11
- nautobot/core/forms/forms.py +33 -2
- nautobot/core/graphql/types.py +1 -1
- nautobot/core/jobs/__init__.py +28 -7
- nautobot/core/jobs/bulk_actions.py +285 -0
- nautobot/core/jobs/cleanup.py +48 -12
- nautobot/core/jobs/groups.py +1 -1
- nautobot/core/management/commands/validate_models.py +1 -1
- nautobot/core/models/__init__.py +3 -1
- nautobot/core/models/query_functions.py +2 -2
- nautobot/core/models/tree_queries.py +6 -3
- nautobot/core/settings.py +29 -2
- nautobot/core/settings.yaml +21 -0
- nautobot/core/tables.py +79 -61
- nautobot/core/templates/about.html +67 -0
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
- nautobot/core/templates/inc/tenant_table_row.html +11 -0
- nautobot/core/templates/nautobot_config.py.j2 +13 -0
- nautobot/core/templates/search.html +7 -0
- nautobot/core/templates/utilities/render_jinja2.html +1 -1
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/theme_preview.html +7 -0
- nautobot/core/templatetags/helpers.py +11 -2
- nautobot/core/testing/__init__.py +8 -8
- nautobot/core/testing/api.py +170 -15
- nautobot/core/testing/filters.py +45 -10
- nautobot/core/testing/forms.py +2 -0
- nautobot/core/testing/integration.py +86 -4
- nautobot/core/testing/mixins.py +7 -2
- nautobot/core/testing/views.py +44 -29
- nautobot/core/tests/integration/test_app_home.py +0 -1
- nautobot/core/tests/integration/test_app_navbar.py +0 -1
- nautobot/core/tests/integration/test_filters.py +0 -2
- nautobot/core/tests/integration/test_home.py +0 -1
- nautobot/core/tests/integration/test_navbar.py +0 -1
- nautobot/core/tests/integration/test_view_authentication.py +1 -0
- nautobot/core/tests/runner.py +1 -1
- nautobot/core/tests/test_api.py +98 -1
- nautobot/core/tests/test_csv.py +25 -3
- nautobot/core/tests/test_filters.py +209 -246
- nautobot/core/tests/test_forms.py +1 -0
- nautobot/core/tests/test_jobs.py +460 -1
- nautobot/core/tests/test_models.py +9 -0
- nautobot/core/tests/test_settings_schema.py +7 -0
- nautobot/core/tests/test_tables.py +100 -0
- nautobot/core/tests/test_utils.py +63 -1
- nautobot/core/tests/test_views.py +30 -3
- nautobot/core/ui/nav.py +1 -0
- nautobot/core/ui/object_detail.py +15 -1
- nautobot/core/urls.py +11 -0
- nautobot/core/utils/lookup.py +11 -8
- nautobot/core/utils/querysets.py +64 -0
- nautobot/core/utils/requests.py +24 -9
- nautobot/core/views/__init__.py +42 -0
- nautobot/core/views/generic.py +131 -197
- nautobot/core/views/mixins.py +126 -38
- nautobot/core/views/renderers.py +6 -6
- nautobot/dcim/api/serializers.py +56 -64
- nautobot/dcim/api/views.py +47 -113
- nautobot/dcim/constants.py +6 -13
- nautobot/dcim/factory.py +6 -1
- nautobot/dcim/filters/__init__.py +31 -2
- nautobot/dcim/forms.py +36 -17
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
- nautobot/dcim/models/__init__.py +1 -1
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +22 -20
- nautobot/dcim/models/devices.py +10 -1
- nautobot/dcim/models/locations.py +3 -3
- nautobot/dcim/models/power.py +6 -5
- nautobot/dcim/models/racks.py +4 -4
- nautobot/dcim/tables/__init__.py +3 -3
- nautobot/dcim/tables/devices.py +7 -5
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/tables/racks.py +1 -1
- nautobot/dcim/templates/dcim/controller_create.html +1 -7
- nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
- nautobot/dcim/templates/dcim/device.html +1 -9
- nautobot/dcim/templates/dcim/device_edit.html +36 -37
- nautobot/dcim/templates/dcim/location.html +1 -9
- nautobot/dcim/templates/dcim/location_edit.html +1 -7
- nautobot/dcim/templates/dcim/rack.html +1 -9
- nautobot/dcim/templates/dcim/rack_edit.html +1 -7
- nautobot/dcim/templates/dcim/rackreservation.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
- nautobot/dcim/tests/integration/test_controller.py +62 -0
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +189 -0
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +181 -0
- nautobot/dcim/tests/test_api.py +16 -5
- nautobot/dcim/tests/test_filters.py +33 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/dcim/tests/test_graphql.py +52 -0
- nautobot/dcim/tests/test_jobs.py +118 -0
- nautobot/dcim/tests/test_models.py +52 -9
- nautobot/dcim/tests/test_views.py +21 -83
- nautobot/dcim/views.py +1 -13
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +90 -85
- nautobot/extras/api/views.py +22 -27
- nautobot/extras/constants.py +2 -0
- nautobot/extras/filters/__init__.py +8 -6
- nautobot/extras/forms/base.py +2 -2
- nautobot/extras/forms/forms.py +139 -31
- nautobot/extras/forms/mixins.py +14 -6
- nautobot/extras/group_sync.py +3 -3
- nautobot/extras/health_checks.py +1 -2
- nautobot/extras/jobs.py +85 -18
- nautobot/extras/managers.py +3 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
- nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
- nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
- nautobot/extras/models/__init__.py +1 -1
- nautobot/extras/models/contacts.py +1 -1
- nautobot/extras/models/customfields.py +12 -11
- nautobot/extras/models/groups.py +11 -9
- nautobot/extras/models/jobs.py +23 -4
- nautobot/extras/models/models.py +2 -2
- nautobot/extras/plugins/__init__.py +13 -2
- nautobot/extras/plugins/marketplace_manifest.yml +84 -79
- nautobot/extras/plugins/tables.py +16 -14
- nautobot/extras/plugins/views.py +65 -69
- nautobot/extras/registry.py +1 -1
- nautobot/extras/secrets/__init__.py +2 -2
- nautobot/extras/tables.py +7 -5
- nautobot/extras/templates/extras/dynamicgroup.html +1 -9
- nautobot/extras/templates/extras/job_detail.html +16 -0
- nautobot/extras/templates/extras/job_edit.html +1 -0
- nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
- nautobot/extras/templates/extras/marketplace.html +29 -11
- nautobot/extras/templates/extras/plugin_detail.html +32 -15
- nautobot/extras/templates/extras/plugins_tiles.html +21 -10
- nautobot/extras/templatetags/job_buttons.py +4 -4
- nautobot/extras/test_jobs/api_test_job.py +1 -1
- nautobot/extras/test_jobs/atomic_transaction.py +2 -2
- nautobot/extras/test_jobs/dry_run.py +1 -1
- nautobot/extras/test_jobs/fail.py +5 -5
- nautobot/extras/test_jobs/file_output.py +1 -1
- nautobot/extras/test_jobs/file_upload_fail.py +1 -1
- nautobot/extras/test_jobs/file_upload_pass.py +1 -1
- nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
- nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
- nautobot/extras/test_jobs/log_redaction.py +1 -1
- nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
- nautobot/extras/test_jobs/modify_db.py +1 -1
- nautobot/extras/test_jobs/object_var_optional.py +1 -1
- nautobot/extras/test_jobs/object_var_required.py +1 -1
- nautobot/extras/test_jobs/object_vars.py +1 -1
- nautobot/extras/test_jobs/pass.py +3 -3
- nautobot/extras/test_jobs/profiling.py +1 -1
- nautobot/extras/test_jobs/relative_import.py +3 -3
- nautobot/extras/test_jobs/singleton.py +16 -0
- nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
- nautobot/extras/test_jobs/task_queues.py +1 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
- nautobot/extras/tests/test_api.py +13 -13
- nautobot/extras/tests/test_customfields.py +1 -1
- nautobot/extras/tests/test_datasources.py +2 -1
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_filters.py +6 -6
- nautobot/extras/tests/test_forms.py +33 -1
- nautobot/extras/tests/test_jobs.py +178 -32
- nautobot/extras/tests/test_models.py +16 -10
- nautobot/extras/tests/test_plugins.py +62 -9
- nautobot/extras/tests/test_relationships.py +120 -9
- nautobot/extras/tests/test_views.py +56 -194
- nautobot/extras/utils.py +3 -2
- nautobot/extras/views.py +30 -98
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +41 -33
- nautobot/ipam/api/views.py +68 -117
- nautobot/ipam/factory.py +1 -1
- nautobot/ipam/filters.py +3 -2
- nautobot/ipam/lookups.py +101 -62
- nautobot/ipam/models.py +66 -16
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tables.py +23 -7
- nautobot/ipam/templates/ipam/ipaddress.html +1 -9
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
- nautobot/ipam/templates/ipam/prefix.html +1 -9
- nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
- nautobot/ipam/templates/ipam/vlan.html +1 -9
- nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
- nautobot/ipam/tests/test_api.py +436 -3
- nautobot/ipam/tests/test_forms.py +49 -47
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_models.py +95 -34
- nautobot/ipam/tests/test_querysets.py +63 -1
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/ipam/utils/__init__.py +36 -6
- nautobot/ipam/views.py +61 -87
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
- nautobot/project-static/docs/404.html +46 -4
- nautobot/project-static/docs/apps/index.html +46 -4
- nautobot/project-static/docs/apps/nautobot-apps.html +47 -6
- nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +62 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +59 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +374 -122
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +90 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +95 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +53 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +52 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +79 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +102 -28
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +108 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +131 -38
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +239 -65
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +581 -165
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +109 -36
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +453 -167
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +493 -211
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +60 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +71 -15
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +407 -55
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +620 -205
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +858 -412
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +59 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +448 -186
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +365 -147
- nautobot/project-static/docs/development/apps/api/configuration-view.html +46 -4
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/global-search.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/graphql.html +46 -4
- nautobot/project-static/docs/development/apps/api/models/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +46 -4
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +68 -7
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +46 -4
- nautobot/project-static/docs/development/apps/api/prometheus.html +46 -4
- nautobot/project-static/docs/development/apps/api/setup.html +46 -4
- nautobot/project-static/docs/development/apps/api/testing.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +46 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/base-template.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/index.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +46 -4
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +52 -6
- nautobot/project-static/docs/development/apps/api/views/urls.html +46 -4
- nautobot/project-static/docs/development/apps/index.html +46 -4
- nautobot/project-static/docs/development/apps/migration/code-updates.html +46 -4
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +46 -4
- nautobot/project-static/docs/development/apps/migration/from-v1.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +46 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +46 -4
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +50 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +46 -4
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +211 -14
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +46 -4
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +46 -4
- nautobot/project-static/docs/development/core/application-registry.html +46 -4
- nautobot/project-static/docs/development/core/best-practices.html +46 -4
- nautobot/project-static/docs/development/core/bootstrap-ui.html +46 -4
- nautobot/project-static/docs/development/core/caching.html +46 -4
- nautobot/project-static/docs/development/core/controllers.html +46 -4
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +73 -74
- nautobot/project-static/docs/development/core/generic-views.html +46 -4
- nautobot/project-static/docs/development/core/getting-started.html +249 -224
- nautobot/project-static/docs/development/core/homepage.html +49 -7
- nautobot/project-static/docs/development/core/index.html +46 -4
- nautobot/project-static/docs/development/core/{local-k8s.html → minikube-dev-environment-for-k8s-jobs.html} +469 -168
- nautobot/project-static/docs/development/core/model-checklist.html +56 -12
- nautobot/project-static/docs/development/core/model-features.html +46 -4
- nautobot/project-static/docs/development/core/natural-keys.html +46 -4
- nautobot/project-static/docs/development/core/navigation-menu.html +46 -4
- nautobot/project-static/docs/development/core/release-checklist.html +49 -7
- nautobot/project-static/docs/development/core/role-internals.html +46 -4
- nautobot/project-static/docs/development/core/settings.html +46 -4
- nautobot/project-static/docs/development/core/style-guide.html +49 -7
- nautobot/project-static/docs/development/core/templates.html +46 -4
- nautobot/project-static/docs/development/core/testing.html +46 -4
- nautobot/project-static/docs/development/core/ui-component-framework.html +369 -273
- nautobot/project-static/docs/development/core/user-preferences.html +46 -4
- nautobot/project-static/docs/development/index.html +46 -4
- nautobot/project-static/docs/development/jobs/index.html +216 -122
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +46 -4
- nautobot/project-static/docs/index.html +54 -23
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +47 -7
- nautobot/project-static/docs/overview/design_philosophy.html +46 -4
- nautobot/project-static/docs/release-notes/index.html +52 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +234 -193
- nautobot/project-static/docs/release-notes/version-1.1.html +231 -190
- nautobot/project-static/docs/release-notes/version-1.2.html +306 -265
- nautobot/project-static/docs/release-notes/version-1.3.html +332 -291
- nautobot/project-static/docs/release-notes/version-1.4.html +417 -377
- nautobot/project-static/docs/release-notes/version-1.5.html +605 -566
- nautobot/project-static/docs/release-notes/version-1.6.html +904 -447
- nautobot/project-static/docs/release-notes/version-2.0.html +528 -489
- nautobot/project-static/docs/release-notes/version-2.1.html +363 -324
- nautobot/project-static/docs/release-notes/version-2.2.html +356 -317
- nautobot/project-static/docs/release-notes/version-2.3.html +997 -352
- nautobot/project-static/docs/release-notes/version-2.4.html +525 -101
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +295 -287
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +48 -6
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +46 -4
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +110 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +46 -4
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +48 -6
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +66 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +47 -5
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +46 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +46 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +46 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +46 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +46 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +49 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +46 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +50 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +49 -7
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +46 -4
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +51 -7
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +46 -4
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +49 -7
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
- nautobot/project-static/docs/user-guide/index.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +50 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +50 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +49 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +47 -5
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +94 -25
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +74 -5
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +46 -4
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +46 -4
- nautobot/project-static/js/forms.js +1 -1
- nautobot/tenancy/api/views.py +9 -13
- nautobot/tenancy/views.py +4 -2
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +5 -4
- nautobot/users/api/views.py +3 -3
- nautobot/virtualization/api/serializers.py +4 -4
- nautobot/virtualization/api/views.py +5 -24
- nautobot/virtualization/filters.py +20 -3
- nautobot/virtualization/models.py +1 -1
- nautobot/virtualization/tables.py +2 -2
- nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
- nautobot/virtualization/tests/test_filters.py +17 -0
- nautobot/wireless/filters.py +2 -2
- nautobot/wireless/forms.py +1 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
- nautobot/wireless/tests/integration/__init__.py +0 -0
- nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
- nautobot/wireless/tests/test_filters.py +29 -1
- nautobot/wireless/tests/test_views.py +22 -1
- nautobot/wireless/views.py +0 -10
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/METADATA +6 -6
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/RECORD +600 -550
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/WHEEL +1 -1
- nautobot/core/fixtures/user-data.json +0 -59
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/NOTICE +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.1.dist-info}/entry_points.txt +0 -0
|
@@ -37,7 +37,9 @@ from nautobot.extras.choices import (
|
|
|
37
37
|
ObjectChangeEventContextChoices,
|
|
38
38
|
)
|
|
39
39
|
from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
|
|
40
|
-
from nautobot.extras.jobs import get_job, get_jobs
|
|
40
|
+
from nautobot.extras.jobs import BaseJob, get_job, get_jobs
|
|
41
|
+
from nautobot.extras.models import Job, JobQueue
|
|
42
|
+
from nautobot.extras.models.jobs import JobLogEntry
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class JobTest(TestCase):
|
|
@@ -45,6 +47,11 @@ class JobTest(TestCase):
|
|
|
45
47
|
Test job features that don't require a transaction test case.
|
|
46
48
|
"""
|
|
47
49
|
|
|
50
|
+
def test_as_form_no_job_model(self):
|
|
51
|
+
"""Job.as_form() test with no corresponding job_model (https://github.com/nautobot/nautobot/issues/6773)."""
|
|
52
|
+
form = BaseJob.as_form()
|
|
53
|
+
self.assertSequenceEqual(list(form.fields.keys()), ["_job_queue", "_profile"])
|
|
54
|
+
|
|
48
55
|
def test_field_default(self):
|
|
49
56
|
"""
|
|
50
57
|
Job test with field that is a default value that is falsey.
|
|
@@ -53,7 +60,7 @@ class JobTest(TestCase):
|
|
|
53
60
|
module = "field_default"
|
|
54
61
|
name = "TestFieldDefault"
|
|
55
62
|
job_class = get_job(f"{module}.{name}")
|
|
56
|
-
form = job_class
|
|
63
|
+
form = job_class.as_form()
|
|
57
64
|
|
|
58
65
|
self.assertInHTML(
|
|
59
66
|
"""<tr><th><label for="id_var_int">Var int:</label></th><td>
|
|
@@ -72,7 +79,7 @@ class JobTest(TestCase):
|
|
|
72
79
|
module = "field_order"
|
|
73
80
|
name = "TestFieldOrder"
|
|
74
81
|
job_class = get_job(f"{module}.{name}")
|
|
75
|
-
form = job_class
|
|
82
|
+
form = job_class.as_form()
|
|
76
83
|
self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_job_queue", "_profile"])
|
|
77
84
|
|
|
78
85
|
def test_no_field_order(self):
|
|
@@ -82,7 +89,7 @@ class JobTest(TestCase):
|
|
|
82
89
|
module = "no_field_order"
|
|
83
90
|
name = "TestNoFieldOrder"
|
|
84
91
|
job_class = get_job(f"{module}.{name}")
|
|
85
|
-
form = job_class
|
|
92
|
+
form = job_class.as_form()
|
|
86
93
|
self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_job_queue", "_profile"])
|
|
87
94
|
|
|
88
95
|
def test_no_field_order_inherited_variable(self):
|
|
@@ -92,7 +99,7 @@ class JobTest(TestCase):
|
|
|
92
99
|
module = "no_field_order"
|
|
93
100
|
name = "TestDefaultFieldOrderWithInheritance"
|
|
94
101
|
job_class = get_job(f"{module}.{name}")
|
|
95
|
-
form = job_class
|
|
102
|
+
form = job_class.as_form()
|
|
96
103
|
self.assertSequenceEqual(
|
|
97
104
|
list(form.fields.keys()),
|
|
98
105
|
["testvar1", "b_testvar2", "a_testvar3", "_job_queue", "_profile"],
|
|
@@ -107,14 +114,14 @@ class JobTest(TestCase):
|
|
|
107
114
|
# not overridden on job model, initial form field value should match job class
|
|
108
115
|
job_model.dryrun_default_override = False
|
|
109
116
|
job_model.save()
|
|
110
|
-
form = job_class
|
|
117
|
+
form = job_class.as_form()
|
|
111
118
|
self.assertEqual(form.fields["dryrun"].initial, job_class.dryrun_default)
|
|
112
119
|
|
|
113
120
|
# overridden on job model, initial form field value should match job model
|
|
114
121
|
job_model.dryrun_default_override = True
|
|
115
122
|
job_model.dryrun_default = not job_class.dryrun_default
|
|
116
123
|
job_model.save()
|
|
117
|
-
form = job_class
|
|
124
|
+
form = job_class.as_form()
|
|
118
125
|
self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
|
|
119
126
|
|
|
120
127
|
def test_job_task_queues_setter(self):
|
|
@@ -143,7 +150,7 @@ class JobTest(TestCase):
|
|
|
143
150
|
name="irrelevant", defaults={"queue_type": JobQueueTypeChoices.TYPE_CELERY}
|
|
144
151
|
)
|
|
145
152
|
job_model.job_queues.set([jq_1, jq_2])
|
|
146
|
-
form = job_class
|
|
153
|
+
form = job_class.as_form()
|
|
147
154
|
self.assertQuerySetEqual(
|
|
148
155
|
form.fields["_job_queue"].queryset,
|
|
149
156
|
models.JobQueue.objects.filter(jobs=job_model),
|
|
@@ -161,7 +168,7 @@ class JobTest(TestCase):
|
|
|
161
168
|
self.assertTrue(job_model.supports_dryrun)
|
|
162
169
|
|
|
163
170
|
module = "pass"
|
|
164
|
-
name = "
|
|
171
|
+
name = "TestPassJob"
|
|
165
172
|
job_class, job_model = get_job_class_and_model(module, name)
|
|
166
173
|
self.assertFalse(job_class.supports_dryrun)
|
|
167
174
|
self.assertFalse(job_model.supports_dryrun)
|
|
@@ -191,27 +198,31 @@ class JobTest(TestCase):
|
|
|
191
198
|
with override_settings(JOBS_ROOT=temp_dir):
|
|
192
199
|
# Create a new Job and make sure it's discovered correctly
|
|
193
200
|
with open(os.path.join(temp_dir, "my_jobs.py"), "w") as fd:
|
|
194
|
-
fd.write(
|
|
201
|
+
fd.write(
|
|
202
|
+
"""\
|
|
195
203
|
from nautobot.apps.jobs import Job, register_jobs
|
|
196
204
|
class MyJob(Job):
|
|
197
205
|
def run(self):
|
|
198
206
|
pass
|
|
199
207
|
register_jobs(MyJob)
|
|
200
|
-
"""
|
|
208
|
+
"""
|
|
209
|
+
)
|
|
201
210
|
jobs_data = get_jobs(reload=True)
|
|
202
211
|
self.assertIn("my_jobs.MyJob", jobs_data.keys())
|
|
203
212
|
self.assertIsNotNone(get_job("my_jobs.MyJob"))
|
|
204
213
|
# Also make sure some representative previous JOBS_ROOT jobs aren't still around:
|
|
205
214
|
self.assertNotIn("dry_run.TestDryRun", jobs_data.keys())
|
|
206
|
-
self.assertNotIn("pass.
|
|
215
|
+
self.assertNotIn("pass.TestPassJob", jobs_data.keys())
|
|
207
216
|
|
|
208
217
|
# Create a second Job in the same module
|
|
209
218
|
with open(os.path.join(temp_dir, "my_jobs.py"), "a") as fd:
|
|
210
|
-
fd.write(
|
|
219
|
+
fd.write(
|
|
220
|
+
"""
|
|
211
221
|
class MyOtherJob(MyJob):
|
|
212
222
|
pass
|
|
213
223
|
register_jobs(MyOtherJob)
|
|
214
|
-
"""
|
|
224
|
+
"""
|
|
225
|
+
)
|
|
215
226
|
jobs_data = get_jobs(reload=True)
|
|
216
227
|
self.assertIn("my_jobs.MyJob", jobs_data.keys())
|
|
217
228
|
self.assertIsNotNone(get_job("my_jobs.MyJob"))
|
|
@@ -220,14 +231,16 @@ register_jobs(MyOtherJob)
|
|
|
220
231
|
|
|
221
232
|
# Create a third Job in another module
|
|
222
233
|
with open(os.path.join(temp_dir, "their_jobs.py"), "w") as fd:
|
|
223
|
-
fd.write(
|
|
234
|
+
fd.write(
|
|
235
|
+
"""
|
|
224
236
|
from nautobot.apps.jobs import Job, register_jobs
|
|
225
237
|
|
|
226
238
|
class MyJob(Job):
|
|
227
239
|
def run(self):
|
|
228
240
|
pass
|
|
229
241
|
register_jobs(MyJob)
|
|
230
|
-
"""
|
|
242
|
+
"""
|
|
243
|
+
)
|
|
231
244
|
jobs_data = get_jobs(reload=True)
|
|
232
245
|
self.assertIn("my_jobs.MyJob", jobs_data.keys())
|
|
233
246
|
self.assertIsNotNone(get_job("my_jobs.MyJob"))
|
|
@@ -249,14 +262,16 @@ register_jobs(MyJob)
|
|
|
249
262
|
|
|
250
263
|
# Create a module with an inauspicious name
|
|
251
264
|
with open(os.path.join(temp_dir, "traceback.py"), "w") as fd:
|
|
252
|
-
fd.write(
|
|
265
|
+
fd.write(
|
|
266
|
+
"""
|
|
253
267
|
from nautobot.apps.jobs import Job, register_jobs
|
|
254
268
|
|
|
255
269
|
class BadJob(Job):
|
|
256
270
|
def run(self):
|
|
257
271
|
raise RuntimeError("You ran a bad job!")
|
|
258
272
|
register_jobs(BadJob)
|
|
259
|
-
"""
|
|
273
|
+
"""
|
|
274
|
+
)
|
|
260
275
|
jobs_data = get_jobs(reload=True)
|
|
261
276
|
self.assertIn("my_jobs.MyJob", jobs_data.keys())
|
|
262
277
|
self.assertIsNotNone(get_job("my_jobs.MyJob"))
|
|
@@ -288,6 +303,105 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
288
303
|
self.request.id = uuid.uuid4()
|
|
289
304
|
self.request.user = self.user
|
|
290
305
|
|
|
306
|
+
def test_bulk_delete_system_jobs_fail(self):
|
|
307
|
+
system_job_queryset = Job.objects.filter(module_name__startswith="nautobot.")
|
|
308
|
+
pk_list = [str(pk) for pk in system_job_queryset.values_list("pk", flat=True)[:3]]
|
|
309
|
+
initial_count = Job.objects.all().count()
|
|
310
|
+
self.add_permissions("extras.delete_job")
|
|
311
|
+
job_result = create_job_result_and_run_job(
|
|
312
|
+
"nautobot.core.jobs.bulk_actions",
|
|
313
|
+
"BulkDeleteObjects",
|
|
314
|
+
content_type=ContentType.objects.get_for_model(Job).id,
|
|
315
|
+
delete_all=False,
|
|
316
|
+
filter_query_params={},
|
|
317
|
+
pk_list=pk_list,
|
|
318
|
+
username=self.user.username,
|
|
319
|
+
)
|
|
320
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
321
|
+
error_log = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
322
|
+
self.assertIn(
|
|
323
|
+
f"Unable to delete Job {system_job_queryset.first()}. System Job cannot be deleted", error_log.message
|
|
324
|
+
)
|
|
325
|
+
self.assertEqual(initial_count, Job.objects.all().count())
|
|
326
|
+
|
|
327
|
+
def test_job_bulk_edit_default_queue_stays_default_after_one_field_update(self):
|
|
328
|
+
self.add_permissions("extras.change_job", "extras.view_job")
|
|
329
|
+
job_class_to_test = "TestPassJob"
|
|
330
|
+
job_description = "default job queue test"
|
|
331
|
+
job_to_test = Job.objects.get(job_class_name=job_class_to_test)
|
|
332
|
+
queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
|
|
333
|
+
default_job_queue = job_to_test.default_job_queue
|
|
334
|
+
pk_list = list(queryset.values_list("pk", flat=True))
|
|
335
|
+
|
|
336
|
+
job_ct = ContentType.objects.get_for_model(Job)
|
|
337
|
+
job_result = create_job_result_and_run_job(
|
|
338
|
+
"nautobot.core.jobs.bulk_actions",
|
|
339
|
+
"BulkEditObjects",
|
|
340
|
+
content_type=job_ct.id,
|
|
341
|
+
edit_all=False,
|
|
342
|
+
filter_query_params={},
|
|
343
|
+
form_data={
|
|
344
|
+
"pk": pk_list,
|
|
345
|
+
"description": job_description,
|
|
346
|
+
},
|
|
347
|
+
username=self.user.username,
|
|
348
|
+
)
|
|
349
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
350
|
+
self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
|
|
351
|
+
self.assertFalse(
|
|
352
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
|
|
353
|
+
)
|
|
354
|
+
self.assertFalse(
|
|
355
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
|
|
356
|
+
)
|
|
357
|
+
instance = Job.objects.get(job_class_name=job_class_to_test)
|
|
358
|
+
self.assertEqual(instance.description, job_description)
|
|
359
|
+
self.assertEqual(instance.default_job_queue, default_job_queue)
|
|
360
|
+
|
|
361
|
+
def test_job_bulk_edit_preserve_job_queues_after_one_field_update(self):
|
|
362
|
+
self.add_permissions("extras.change_job", "extras.view_job")
|
|
363
|
+
job_class_to_test = "TestPassJob"
|
|
364
|
+
job_description = "job queues test"
|
|
365
|
+
job_to_test = Job.objects.get(job_class_name=job_class_to_test)
|
|
366
|
+
|
|
367
|
+
# Simulate 3 job queues set
|
|
368
|
+
job_queues = JobQueue.objects.all()[:3]
|
|
369
|
+
job_to_test.job_queues.set(job_queues)
|
|
370
|
+
job_to_test.job_queues.add(job_to_test.default_job_queue)
|
|
371
|
+
job_to_test.job_queues_override = True
|
|
372
|
+
job_to_test.save()
|
|
373
|
+
|
|
374
|
+
expected_job_queues = list(job_to_test.job_queues.values_list("pk", flat=True))
|
|
375
|
+
|
|
376
|
+
queryset = Job.objects.filter(installed=True, hidden=False, job_class_name=job_class_to_test)
|
|
377
|
+
pk_list = list(queryset.values_list("pk", flat=True))
|
|
378
|
+
|
|
379
|
+
job_ct = ContentType.objects.get_for_model(Job)
|
|
380
|
+
job_result = create_job_result_and_run_job(
|
|
381
|
+
"nautobot.core.jobs.bulk_actions",
|
|
382
|
+
"BulkEditObjects",
|
|
383
|
+
content_type=job_ct.id,
|
|
384
|
+
edit_all=False,
|
|
385
|
+
filter_query_params={},
|
|
386
|
+
form_data={
|
|
387
|
+
"pk": pk_list,
|
|
388
|
+
"description": job_description,
|
|
389
|
+
},
|
|
390
|
+
username=self.user.username,
|
|
391
|
+
)
|
|
392
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
393
|
+
self.assertEqual(Job.objects.filter(description=job_description).count(), queryset.count())
|
|
394
|
+
self.assertFalse(
|
|
395
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING).exists()
|
|
396
|
+
)
|
|
397
|
+
self.assertFalse(
|
|
398
|
+
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR).exists()
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
instance = Job.objects.get(job_class_name=job_class_to_test)
|
|
402
|
+
self.assertEqual(instance.description, job_description)
|
|
403
|
+
self.assertQuerySetEqual(instance.job_queues.all(), JobQueue.objects.filter(pk__in=expected_job_queues))
|
|
404
|
+
|
|
291
405
|
def test_job_hard_time_limit_less_than_soft_time_limit(self):
|
|
292
406
|
"""
|
|
293
407
|
Job test which produces a warning log message because the time_limit is less than the soft_time_limit.
|
|
@@ -310,7 +424,7 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
310
424
|
Job test with pass result.
|
|
311
425
|
"""
|
|
312
426
|
module = "pass"
|
|
313
|
-
name = "
|
|
427
|
+
name = "TestPassJob"
|
|
314
428
|
job_result = create_job_result_and_run_job(module, name)
|
|
315
429
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
316
430
|
self.assertEqual(job_result.result, True)
|
|
@@ -344,7 +458,7 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
344
458
|
Job test with fail result.
|
|
345
459
|
"""
|
|
346
460
|
module = "fail"
|
|
347
|
-
name = "
|
|
461
|
+
name = "TestFailJob"
|
|
348
462
|
job_result = create_job_result_and_run_job(module, name)
|
|
349
463
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
350
464
|
logs = job_result.job_log_entries
|
|
@@ -467,7 +581,7 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
467
581
|
"ipv6_with_mask": "2001:db8::1/64",
|
|
468
582
|
"ipv6_network": "2001:db8::/64",
|
|
469
583
|
}
|
|
470
|
-
form = job_class
|
|
584
|
+
form = job_class.as_form(form_data)
|
|
471
585
|
self.assertTrue(form.is_valid())
|
|
472
586
|
|
|
473
587
|
# Prepare the job data
|
|
@@ -579,7 +693,7 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
579
693
|
Job test to see if the latest_result property is indeed returning the most recent job result
|
|
580
694
|
"""
|
|
581
695
|
module = "pass"
|
|
582
|
-
name = "
|
|
696
|
+
name = "TestPassJob"
|
|
583
697
|
job_result_1 = create_job_result_and_run_job(module, name)
|
|
584
698
|
self.assertEqual(job_result_1.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
585
699
|
job_result_2 = create_job_result_and_run_job(module, name)
|
|
@@ -606,6 +720,38 @@ class JobTransactionTest(TransactionTestCase):
|
|
|
606
720
|
self.assertTrue(profiling_result.exists())
|
|
607
721
|
profiling_result.unlink()
|
|
608
722
|
|
|
723
|
+
def test_job_singleton(self):
|
|
724
|
+
module = "singleton"
|
|
725
|
+
name = "TestSingletonJob"
|
|
726
|
+
|
|
727
|
+
job_class, _ = get_job_class_and_model(module, name, "local")
|
|
728
|
+
self.assertTrue(job_class.is_singleton)
|
|
729
|
+
cache.set(job_class.singleton_cache_key, 1)
|
|
730
|
+
failed_job_result = create_job_result_and_run_job(module, name)
|
|
731
|
+
|
|
732
|
+
self.assertEqual(
|
|
733
|
+
failed_job_result.status, JobResultStatusChoices.STATUS_FAILURE, msg="Duplicate singleton job didn't error."
|
|
734
|
+
)
|
|
735
|
+
self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
|
|
736
|
+
|
|
737
|
+
def test_job_ignore_singleton(self):
|
|
738
|
+
module = "singleton"
|
|
739
|
+
name = "TestSingletonJob"
|
|
740
|
+
|
|
741
|
+
job_class, _ = get_job_class_and_model(module, name, "local")
|
|
742
|
+
self.assertTrue(job_class.is_singleton)
|
|
743
|
+
cache.set(job_class.singleton_cache_key, 1)
|
|
744
|
+
passed_job_result = create_job_result_and_run_job(
|
|
745
|
+
module, name, celery_kwargs={"nautobot_job_ignore_singleton_lock": True}
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
self.assertEqual(
|
|
749
|
+
passed_job_result.status,
|
|
750
|
+
JobResultStatusChoices.STATUS_SUCCESS,
|
|
751
|
+
msg="Duplicate singleton job didn't succeed with nautobot_job_ignore_singleton_lock=True.",
|
|
752
|
+
)
|
|
753
|
+
self.assertIsNone(cache.get(job_class.singleton_cache_key, None))
|
|
754
|
+
|
|
609
755
|
@mock.patch("nautobot.extras.context_managers.enqueue_webhooks")
|
|
610
756
|
def test_job_fires_webhooks(self, mock_enqueue_webhooks):
|
|
611
757
|
module = "atomic_transaction"
|
|
@@ -645,7 +791,7 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
645
791
|
|
|
646
792
|
# Serialize the file to FileProxy
|
|
647
793
|
data = {"file": self.test_file}
|
|
648
|
-
form = job_class
|
|
794
|
+
form = job_class.as_form(files=data)
|
|
649
795
|
self.assertTrue(form.is_valid())
|
|
650
796
|
serialized_data = job_class.serialize_data(form.cleaned_data)
|
|
651
797
|
|
|
@@ -675,7 +821,7 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
675
821
|
|
|
676
822
|
# Serialize the file to FileProxy
|
|
677
823
|
data = {"file": self.test_file}
|
|
678
|
-
form = job_class
|
|
824
|
+
form = job_class.as_form(files=data)
|
|
679
825
|
self.assertTrue(form.is_valid())
|
|
680
826
|
serialized_data = job_class.serialize_data(form.cleaned_data)
|
|
681
827
|
|
|
@@ -794,7 +940,7 @@ class RunJobManagementCommandTest(TransactionTestCase):
|
|
|
794
940
|
def test_runjob_nochange_successful(self):
|
|
795
941
|
"""Basic success-path test for Jobs that don't modify the Nautobot database."""
|
|
796
942
|
module = "pass"
|
|
797
|
-
name = "
|
|
943
|
+
name = "TestPassJob"
|
|
798
944
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
799
945
|
|
|
800
946
|
out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
|
|
@@ -880,7 +1026,7 @@ class JobButtonReceiverTest(TestCase):
|
|
|
880
1026
|
module = "job_button_receiver"
|
|
881
1027
|
name = "TestJobButtonReceiverSimple"
|
|
882
1028
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
883
|
-
form = job_class
|
|
1029
|
+
form = job_class.as_form()
|
|
884
1030
|
self.assertSequenceEqual(list(form.fields.keys()), ["object_pk", "object_model_name", "_job_queue", "_profile"])
|
|
885
1031
|
|
|
886
1032
|
def test_hidden(self):
|
|
@@ -892,7 +1038,7 @@ class JobButtonReceiverTest(TestCase):
|
|
|
892
1038
|
def test_is_job_button(self):
|
|
893
1039
|
with self.subTest(expected=False):
|
|
894
1040
|
module = "pass"
|
|
895
|
-
name = "
|
|
1041
|
+
name = "TestPassJob"
|
|
896
1042
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
897
1043
|
self.assertFalse(job_model.is_job_button_receiver)
|
|
898
1044
|
|
|
@@ -943,7 +1089,7 @@ class JobHookReceiverTest(TestCase):
|
|
|
943
1089
|
module = "job_hook_receiver"
|
|
944
1090
|
name = "TestJobHookReceiverLog"
|
|
945
1091
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
946
|
-
form = job_class
|
|
1092
|
+
form = job_class.as_form()
|
|
947
1093
|
self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_job_queue", "_profile"])
|
|
948
1094
|
|
|
949
1095
|
def test_hidden(self):
|
|
@@ -955,7 +1101,7 @@ class JobHookReceiverTest(TestCase):
|
|
|
955
1101
|
def test_is_job_hook(self):
|
|
956
1102
|
with self.subTest(expected=False):
|
|
957
1103
|
module = "pass"
|
|
958
|
-
name = "
|
|
1104
|
+
name = "TestPassJob"
|
|
959
1105
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
960
1106
|
self.assertFalse(job_model.is_job_hook_receiver)
|
|
961
1107
|
|
|
@@ -1116,7 +1262,7 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
1116
1262
|
for i in range(1, 7):
|
|
1117
1263
|
models.ScheduledJob.objects.create(
|
|
1118
1264
|
name=f"test{i}",
|
|
1119
|
-
task="pass.
|
|
1265
|
+
task="pass.TestPassJob",
|
|
1120
1266
|
interval=JobExecutionType.TYPE_FUTURE,
|
|
1121
1267
|
user=self.user,
|
|
1122
1268
|
start_time=timezone.now() - datetime.timedelta(days=i * 30),
|
|
@@ -1125,7 +1271,7 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
1125
1271
|
|
|
1126
1272
|
models.ScheduledJob.objects.create(
|
|
1127
1273
|
name="test7",
|
|
1128
|
-
task="pass.
|
|
1274
|
+
task="pass.TestPassJob",
|
|
1129
1275
|
interval=JobExecutionType.TYPE_DAILY,
|
|
1130
1276
|
user=self.user,
|
|
1131
1277
|
start_time=timezone.now() - datetime.timedelta(days=180),
|
|
@@ -1153,7 +1299,7 @@ class ScheduledJobIntervalTestCase(TestCase):
|
|
|
1153
1299
|
start_time = timezone.now() + datetime.timedelta(days=6)
|
|
1154
1300
|
scheduled_job = models.ScheduledJob.objects.create(
|
|
1155
1301
|
name="weekly_interval",
|
|
1156
|
-
task="pass.
|
|
1302
|
+
task="pass.TestPassJob",
|
|
1157
1303
|
interval=JobExecutionType.TYPE_WEEKLY,
|
|
1158
1304
|
user=self.user,
|
|
1159
1305
|
start_time=start_time,
|
|
@@ -1083,7 +1083,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
|
|
|
1083
1083
|
@classmethod
|
|
1084
1084
|
def setUpTestData(cls):
|
|
1085
1085
|
# JobModel instances are automatically instantiated at startup, so we just need to look them up.
|
|
1086
|
-
cls.local_job = JobModel.objects.get(job_class_name="
|
|
1086
|
+
cls.local_job = JobModel.objects.get(job_class_name="TestPassJob")
|
|
1087
1087
|
cls.job_containing_sensitive_variables = JobModel.objects.get(job_class_name="ExampleLoggingJob")
|
|
1088
1088
|
cls.app_job = JobModel.objects.get(job_class_name="ExampleJob")
|
|
1089
1089
|
|
|
@@ -1095,7 +1095,7 @@ class JobModelTest(ModelTestCases.BaseModelTestCase):
|
|
|
1095
1095
|
self.assertEqual(self.app_job.job_class, ExampleJob)
|
|
1096
1096
|
|
|
1097
1097
|
def test_class_path(self):
|
|
1098
|
-
self.assertEqual(self.local_job.class_path, "pass.
|
|
1098
|
+
self.assertEqual(self.local_job.class_path, "pass.TestPassJob")
|
|
1099
1099
|
self.assertIsNotNone(self.local_job.job_class)
|
|
1100
1100
|
self.assertEqual(self.local_job.class_path, self.local_job.job_class.class_path)
|
|
1101
1101
|
|
|
@@ -1832,11 +1832,11 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
1832
1832
|
|
|
1833
1833
|
def setUp(self):
|
|
1834
1834
|
self.user = User.objects.create_user(username="scheduledjobuser")
|
|
1835
|
-
self.job_model = JobModel.objects.get(name="
|
|
1835
|
+
self.job_model = JobModel.objects.get(name="TestPassJob")
|
|
1836
1836
|
|
|
1837
1837
|
self.daily_utc_job = ScheduledJob.objects.create(
|
|
1838
1838
|
name="Daily UTC Job",
|
|
1839
|
-
task="pass.
|
|
1839
|
+
task="pass.TestPassJob",
|
|
1840
1840
|
job_model=self.job_model,
|
|
1841
1841
|
interval=JobExecutionType.TYPE_DAILY,
|
|
1842
1842
|
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=get_default_timezone()),
|
|
@@ -1844,7 +1844,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
1844
1844
|
)
|
|
1845
1845
|
self.daily_est_job = ScheduledJob.objects.create(
|
|
1846
1846
|
name="Daily EST Job",
|
|
1847
|
-
task="pass.
|
|
1847
|
+
task="pass.TestPassJob",
|
|
1848
1848
|
job_model=self.job_model,
|
|
1849
1849
|
interval=JobExecutionType.TYPE_DAILY,
|
|
1850
1850
|
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
@@ -1859,7 +1859,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
1859
1859
|
)
|
|
1860
1860
|
self.crontab_est_job = ScheduledJob.objects.create(
|
|
1861
1861
|
name="Crontab EST Job",
|
|
1862
|
-
task="pass.
|
|
1862
|
+
task="pass.TestPassJob",
|
|
1863
1863
|
job_model=self.job_model,
|
|
1864
1864
|
interval=JobExecutionType.TYPE_CUSTOM,
|
|
1865
1865
|
start_time=datetime(year=2050, month=1, day=22, hour=17, minute=0, tzinfo=ZoneInfo("America/New_York")),
|
|
@@ -1868,7 +1868,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
1868
1868
|
)
|
|
1869
1869
|
self.one_off_utc_job = ScheduledJob.objects.create(
|
|
1870
1870
|
name="One-off UTC Job",
|
|
1871
|
-
task="pass.
|
|
1871
|
+
task="pass.TestPassJob",
|
|
1872
1872
|
job_model=self.job_model,
|
|
1873
1873
|
interval=JobExecutionType.TYPE_FUTURE,
|
|
1874
1874
|
start_time=datetime(year=2050, month=1, day=22, hour=0, minute=0, tzinfo=ZoneInfo("UTC")),
|
|
@@ -2015,7 +2015,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
2015
2015
|
"""Test that TYPE_CUSTOM behavior around DST is as expected."""
|
|
2016
2016
|
cronjob = ScheduledJob.objects.create(
|
|
2017
2017
|
name="DST Aware Cronjob",
|
|
2018
|
-
task="pass.
|
|
2018
|
+
task="pass.TestPassJob",
|
|
2019
2019
|
job_model=self.job_model,
|
|
2020
2020
|
enabled=False,
|
|
2021
2021
|
interval=JobExecutionType.TYPE_CUSTOM,
|
|
@@ -2070,7 +2070,7 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
2070
2070
|
"""Test the interaction of TYPE_DAILY around DST."""
|
|
2071
2071
|
daily = ScheduledJob.objects.create(
|
|
2072
2072
|
name="Daily Job",
|
|
2073
|
-
task="pass.
|
|
2073
|
+
task="pass.TestPassJob",
|
|
2074
2074
|
job_model=self.job_model,
|
|
2075
2075
|
enabled=False,
|
|
2076
2076
|
interval=JobExecutionType.TYPE_DAILY,
|
|
@@ -2146,6 +2146,12 @@ class ScheduledJobTest(ModelTestCases.BaseModelTestCase):
|
|
|
2146
2146
|
# entry = NautobotScheduleEntry(model=sj)
|
|
2147
2147
|
# scheduler = NautobotDatabaseScheduler(app=entry.app)
|
|
2148
2148
|
# scheduler.apply_async(entry=entry, producer=None, advance=False)
|
|
2149
|
+
# Check scheduled job runs correctly with no job queue
|
|
2150
|
+
# sj.job_queue = None
|
|
2151
|
+
# sj.save()
|
|
2152
|
+
# entry = NautobotScheduleEntry(model=sj)
|
|
2153
|
+
# scheduler = NautobotDatabaseScheduler(app=entry.app)
|
|
2154
|
+
# scheduler.apply_async(entry=entry, producer=None, advance=False)
|
|
2149
2155
|
|
|
2150
2156
|
|
|
2151
2157
|
class SecretTest(ModelTestCases.BaseModelTestCase):
|
|
@@ -2638,7 +2644,7 @@ class JobLogEntryTest(TestCase): # TODO: change to BaseModelTestCase
|
|
|
2638
2644
|
|
|
2639
2645
|
def setUp(self):
|
|
2640
2646
|
module = "pass"
|
|
2641
|
-
name = "
|
|
2647
|
+
name = "TestPassJob"
|
|
2642
2648
|
job_class = get_job(f"{module}.{name}")
|
|
2643
2649
|
|
|
2644
2650
|
self.job_result = JobResult.objects.create(name=job_class.class_path, user=None)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from unittest import mock, skip
|
|
1
|
+
from unittest import mock, skip
|
|
2
2
|
|
|
3
|
+
from django.apps import apps
|
|
3
4
|
from django.conf import settings
|
|
4
5
|
from django.contrib.contenttypes.models import ContentType
|
|
5
6
|
from django.core.exceptions import ValidationError
|
|
@@ -21,6 +22,7 @@ from nautobot.extras.models import CustomField, Relationship, RelationshipAssoci
|
|
|
21
22
|
from nautobot.extras.plugins.exceptions import PluginImproperlyConfigured
|
|
22
23
|
from nautobot.extras.plugins.utils import load_plugin
|
|
23
24
|
from nautobot.extras.plugins.validators import wrap_model_clean_methods
|
|
25
|
+
from nautobot.extras.plugins.views import extract_app_data
|
|
24
26
|
from nautobot.extras.registry import DatasourceContent, registry
|
|
25
27
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix
|
|
26
28
|
from nautobot.tenancy.filters import TenantFilterSet
|
|
@@ -315,6 +317,61 @@ class AppListViewTest(TestCase):
|
|
|
315
317
|
response_body = extract_page_body(response.content.decode(response.charset)).lower()
|
|
316
318
|
self.assertIn("example app", response_body, msg=response_body)
|
|
317
319
|
|
|
320
|
+
def test_extract_app_data(self):
|
|
321
|
+
app_config = apps.get_app_config("example_app")
|
|
322
|
+
# Without a corresponding entry in marketplace_data
|
|
323
|
+
self.assertEqual(
|
|
324
|
+
extract_app_data(app_config, {"apps": []}),
|
|
325
|
+
{
|
|
326
|
+
"name": "Example Nautobot App",
|
|
327
|
+
"package": "example_app",
|
|
328
|
+
"app_label": "example_app",
|
|
329
|
+
"author": "Nautobot development team",
|
|
330
|
+
"author_email": "nautobot@example.com",
|
|
331
|
+
"headline": "For testing purposes only",
|
|
332
|
+
"description": "For testing purposes only",
|
|
333
|
+
"version": "1.0.0",
|
|
334
|
+
"home_url": "plugins:example_app:home",
|
|
335
|
+
"config_url": "plugins:example_app:config",
|
|
336
|
+
"docs_url": "plugins:example_app:docs",
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# With a corresponding entry in marketplace_data
|
|
341
|
+
mock_marketplace_data = {
|
|
342
|
+
"apps": [
|
|
343
|
+
{
|
|
344
|
+
"name": "Nautobot Example App",
|
|
345
|
+
"use_cases": ["Example", "Development"],
|
|
346
|
+
"requires": [],
|
|
347
|
+
"docs": "https://example.com/docs/example_app/",
|
|
348
|
+
"headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
|
|
349
|
+
"description": "An example of the kinds of things a Nautobot App can do, including ...",
|
|
350
|
+
"package_name": "example_app",
|
|
351
|
+
"author": "Network to Code (NTC)",
|
|
352
|
+
"availability": "Open Source",
|
|
353
|
+
"icon": "...",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
}
|
|
357
|
+
self.assertEqual(
|
|
358
|
+
extract_app_data(app_config, mock_marketplace_data),
|
|
359
|
+
{
|
|
360
|
+
"name": "Nautobot Example App",
|
|
361
|
+
"package": "example_app",
|
|
362
|
+
"app_label": "example_app",
|
|
363
|
+
"author": "Network to Code (NTC)",
|
|
364
|
+
"author_email": "nautobot@example.com",
|
|
365
|
+
"availability": "Open Source",
|
|
366
|
+
"headline": "Demonstrate and test various parts of Nautobot App APIs and functionality.",
|
|
367
|
+
"description": "An example of the kinds of things a Nautobot App can do, including ...",
|
|
368
|
+
"version": "1.0.0",
|
|
369
|
+
"home_url": "plugins:example_app:home",
|
|
370
|
+
"config_url": "plugins:example_app:config",
|
|
371
|
+
"docs_url": "plugins:example_app:docs",
|
|
372
|
+
},
|
|
373
|
+
)
|
|
374
|
+
|
|
318
375
|
|
|
319
376
|
class PluginDetailViewTest(TestCase):
|
|
320
377
|
def test_view_detail_anonymous(self):
|
|
@@ -800,10 +857,6 @@ class TestAppCoreViewOverrides(TestCase):
|
|
|
800
857
|
)
|
|
801
858
|
|
|
802
859
|
|
|
803
|
-
@skipIf(
|
|
804
|
-
"example_plugin" not in settings.PLUGINS,
|
|
805
|
-
"example_plugin not in settings.PLUGINS",
|
|
806
|
-
)
|
|
807
860
|
class PluginTemplateExtensionsTest(TestCase):
|
|
808
861
|
"""
|
|
809
862
|
Test that registered TemplateExtensions inject content as expected
|
|
@@ -823,19 +876,19 @@ class PluginTemplateExtensionsTest(TestCase):
|
|
|
823
876
|
def test_detail_view_buttons(self):
|
|
824
877
|
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
825
878
|
response_body = extract_page_body(response.content.decode(response.charset))
|
|
826
|
-
self.assertIn("LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
|
|
879
|
+
self.assertIn("APP INJECTED LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
|
|
827
880
|
|
|
828
881
|
def test_detail_view_left_page(self):
|
|
829
882
|
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
830
883
|
response_body = extract_page_body(response.content.decode(response.charset))
|
|
831
|
-
self.assertIn("
|
|
884
|
+
self.assertIn("App Injected Content - Left", response_body, msg=response_body)
|
|
832
885
|
|
|
833
886
|
def test_detail_view_right_page(self):
|
|
834
887
|
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
835
888
|
response_body = extract_page_body(response.content.decode(response.charset))
|
|
836
|
-
self.assertIn("
|
|
889
|
+
self.assertIn("App Injected Content - Right", response_body, msg=response_body)
|
|
837
890
|
|
|
838
891
|
def test_detail_view_full_width_page(self):
|
|
839
892
|
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
840
893
|
response_body = extract_page_body(response.content.decode(response.charset))
|
|
841
|
-
self.assertIn("
|
|
894
|
+
self.assertIn("App Injected Content - Full Width", response_body, msg=response_body)
|