nautobot 2.0.0a2__py3-none-any.whl → 2.0.0b1__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.
- nautobot/__init__.py +1 -5
- nautobot/apps/api.py +6 -8
- nautobot/apps/forms.py +0 -2
- nautobot/apps/ui.py +0 -8
- nautobot/circuits/api/serializers.py +9 -119
- nautobot/circuits/api/urls.py +1 -1
- nautobot/circuits/api/views.py +0 -1
- nautobot/circuits/choices.py +0 -2
- nautobot/circuits/filters.py +7 -6
- nautobot/circuits/forms.py +3 -73
- nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
- nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
- nautobot/circuits/migrations/0003_auto_slug.py +0 -1
- nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
- nautobot/circuits/migrations/0005_providernetwork.py +0 -1
- nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
- nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
- nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
- nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
- nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
- nautobot/circuits/migrations/0012_created_datetime.py +0 -1
- nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
- nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
- nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
- nautobot/circuits/migrations/0016_tagsfield.py +34 -0
- nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
- nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
- nautobot/circuits/models.py +3 -93
- nautobot/circuits/navigation.py +14 -69
- nautobot/circuits/signals.py +0 -2
- nautobot/circuits/tables.py +42 -5
- nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
- nautobot/circuits/templates/circuits/provider_create.html +0 -1
- nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
- nautobot/circuits/tests/integration/test_relationships.py +13 -16
- nautobot/circuits/tests/test_api.py +13 -43
- nautobot/circuits/tests/test_filters.py +20 -15
- nautobot/circuits/tests/test_models.py +7 -3
- nautobot/circuits/tests/test_views.py +57 -67
- nautobot/circuits/views.py +18 -9
- nautobot/core/api/__init__.py +8 -2
- nautobot/core/api/authentication.py +0 -3
- nautobot/core/api/fields.py +15 -6
- nautobot/core/api/filter_backends.py +3 -2
- nautobot/core/api/metadata.py +237 -30
- nautobot/core/api/mixins.py +94 -0
- nautobot/core/api/pagination.py +3 -3
- nautobot/core/api/parsers.py +154 -0
- nautobot/core/api/renderers.py +153 -2
- nautobot/core/api/schema.py +47 -3
- nautobot/core/api/serializers.py +377 -37
- nautobot/core/api/urls.py +11 -3
- nautobot/core/api/utils.py +174 -2
- nautobot/core/api/versioning.py +32 -10
- nautobot/core/api/views.py +266 -75
- nautobot/core/apps/__init__.py +138 -221
- nautobot/core/celery/__init__.py +112 -41
- nautobot/core/celery/backends.py +19 -13
- nautobot/core/celery/control.py +46 -0
- nautobot/core/celery/encoders.py +53 -0
- nautobot/core/celery/log.py +38 -0
- nautobot/core/celery/schedulers.py +23 -4
- nautobot/core/celery/task.py +1 -16
- nautobot/core/checks.py +0 -27
- nautobot/core/choices.py +21 -113
- nautobot/core/{cli.py → cli/__init__.py} +1 -2
- nautobot/core/cli/__main__.py +3 -0
- nautobot/core/constants.py +25 -43
- nautobot/core/context_processors.py +12 -0
- nautobot/core/filters.py +2 -2
- nautobot/core/forms/__init__.py +0 -4
- nautobot/core/forms/fields.py +39 -68
- nautobot/core/forms/forms.py +27 -27
- nautobot/core/forms/utils.py +7 -59
- nautobot/core/forms/widgets.py +0 -1
- nautobot/core/graphql/__init__.py +2 -2
- nautobot/core/graphql/schema.py +4 -27
- nautobot/core/jobs/__init__.py +75 -0
- nautobot/core/management/commands/build_ui.py +255 -0
- nautobot/core/management/commands/celery.py +0 -1
- nautobot/core/management/commands/generate_test_data.py +18 -13
- nautobot/core/management/commands/post_upgrade.py +24 -24
- nautobot/core/management/commands/validate_models.py +0 -1
- nautobot/core/middleware.py +0 -1
- nautobot/core/models/__init__.py +26 -1
- nautobot/core/models/fields.py +24 -5
- nautobot/core/models/generics.py +2 -46
- nautobot/core/models/managers.py +5 -0
- nautobot/core/models/name_color_content_types.py +1 -19
- nautobot/core/models/tree_queries.py +14 -4
- nautobot/core/models/utils.py +9 -10
- nautobot/core/models/validators.py +17 -8
- nautobot/core/releases.py +8 -10
- nautobot/core/settings.py +81 -53
- nautobot/core/tables.py +5 -5
- nautobot/core/tasks.py +4 -7
- nautobot/core/templates/base.html +1 -49
- nautobot/core/templates/base_django.html +49 -0
- nautobot/core/templates/base_react.html +55 -0
- nautobot/core/templates/buttons/export.html +6 -4
- nautobot/core/templates/generic/object_bulk_create.html +10 -21
- nautobot/core/templates/generic/object_list.html +4 -1
- nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
- nautobot/core/templates/inc/footer.html +1 -0
- nautobot/core/templates/inc/javascript.html +0 -14
- nautobot/core/templates/inc/nav_menu.html +28 -33
- nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
- nautobot/core/templates/inc/relationships_table_rows.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +8 -25
- nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
- nautobot/core/templates/rest_framework/api.html +8 -0
- nautobot/core/templatetags/buttons.py +32 -29
- nautobot/core/templatetags/helpers.py +1 -1
- nautobot/core/testing/__init__.py +47 -44
- nautobot/core/testing/api.py +365 -47
- nautobot/core/testing/filters.py +12 -7
- nautobot/core/testing/integration.py +1 -1
- nautobot/core/testing/migrations.py +2 -0
- nautobot/core/testing/mixins.py +22 -12
- nautobot/core/testing/schema.py +2 -1
- nautobot/core/testing/views.py +28 -51
- nautobot/core/tests/integration/test_filters.py +17 -8
- nautobot/core/tests/integration/test_navbar.py +11 -34
- nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
- nautobot/core/tests/nautobot_config.py +2 -3
- nautobot/core/tests/runner.py +0 -1
- nautobot/core/tests/test_api.py +290 -24
- nautobot/core/tests/test_authentication.py +57 -14
- nautobot/core/tests/test_checks.py +0 -7
- nautobot/core/tests/test_choices.py +0 -1
- nautobot/core/tests/test_filters.py +117 -110
- nautobot/core/tests/test_forms.py +47 -110
- nautobot/core/tests/test_graphql.py +158 -135
- nautobot/core/tests/test_logging.py +4 -1
- nautobot/core/tests/test_managers.py +3 -5
- nautobot/core/tests/test_models.py +2 -0
- nautobot/core/tests/test_ordering.py +0 -2
- nautobot/core/tests/test_paginator.py +3 -1
- nautobot/core/tests/test_releases.py +12 -12
- nautobot/core/tests/test_templatetags_helpers.py +7 -4
- nautobot/core/tests/test_utils.py +112 -78
- nautobot/core/tests/test_views.py +12 -17
- nautobot/core/tests/test_views_utils.py +6 -9
- nautobot/core/utils/data.py +17 -0
- nautobot/core/utils/deprecation.py +13 -20
- nautobot/core/utils/filtering.py +53 -9
- nautobot/core/utils/git.py +12 -4
- nautobot/core/utils/lookup.py +3 -1
- nautobot/core/utils/requests.py +23 -116
- nautobot/core/views/__init__.py +1 -2
- nautobot/core/views/generic.py +131 -119
- nautobot/core/views/mixins.py +53 -62
- nautobot/core/views/paginator.py +0 -1
- nautobot/core/views/renderers.py +14 -12
- nautobot/core/views/utils.py +87 -4
- nautobot/dcim/api/serializers.py +160 -672
- nautobot/dcim/api/urls.py +1 -1
- nautobot/dcim/api/views.py +7 -46
- nautobot/dcim/choices.py +2 -25
- nautobot/dcim/elevations.py +0 -1
- nautobot/dcim/factory.py +15 -4
- nautobot/dcim/filters/__init__.py +42 -13
- nautobot/dcim/form_mixins.py +1 -27
- nautobot/dcim/forms.py +58 -797
- nautobot/dcim/management/commands/trace_paths.py +0 -1
- nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
- nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
- nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
- nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
- nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
- nautobot/dcim/migrations/0006_auto_slug.py +0 -1
- nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
- nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
- nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
- nautobot/dcim/migrations/0010_interface_status.py +0 -1
- nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
- nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
- nautobot/dcim/migrations/0013_location_location_type.py +0 -1
- nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
- nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
- nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
- nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
- nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
- nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
- nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
- nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
- nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
- nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
- nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
- nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
- nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
- nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
- nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
- nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
- nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
- nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
- nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
- nautobot/dcim/migrations/0033_created_datetime.py +0 -1
- nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
- nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
- nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
- nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
- nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
- nautobot/dcim/migrations/0039_remove_slug.py +24 -0
- nautobot/dcim/migrations/0040_tagsfield.py +109 -0
- nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
- nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
- nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
- nautobot/dcim/models/cables.py +4 -35
- nautobot/dcim/models/device_component_templates.py +7 -2
- nautobot/dcim/models/device_components.py +26 -203
- nautobot/dcim/models/devices.py +30 -152
- nautobot/dcim/models/locations.py +3 -64
- nautobot/dcim/models/power.py +3 -51
- nautobot/dcim/models/racks.py +7 -86
- nautobot/dcim/navigation.py +141 -467
- nautobot/dcim/signals.py +0 -2
- nautobot/dcim/tables/devices.py +8 -5
- nautobot/dcim/tables/devicetypes.py +1 -1
- nautobot/dcim/tables/locations.py +2 -2
- nautobot/dcim/tables/power.py +2 -2
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/device.html +15 -4
- nautobot/dcim/templates/dcim/device_edit.html +6 -0
- nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
- nautobot/dcim/templates/dcim/devicetype.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +4 -0
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/location.html +16 -1
- nautobot/dcim/templates/dcim/locationtype.html +15 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/rackgroup.html +0 -12
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +202 -130
- nautobot/dcim/tests/test_cablepaths.py +47 -42
- nautobot/dcim/tests/test_filters.py +156 -134
- nautobot/dcim/tests/test_forms.py +12 -213
- nautobot/dcim/tests/test_graphql.py +8 -3
- nautobot/dcim/tests/test_migrations.py +6 -11
- nautobot/dcim/tests/test_models.py +208 -158
- nautobot/dcim/tests/test_natural_ordering.py +12 -14
- nautobot/dcim/tests/test_signals.py +7 -4
- nautobot/dcim/tests/test_views.py +270 -264
- nautobot/dcim/urls.py +21 -26
- nautobot/dcim/views.py +14 -156
- nautobot/docs/additional-features/caching.md +6 -87
- nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
- nautobot/docs/additional-features/jobs.md +179 -197
- nautobot/docs/administration/nautobot-server.md +9 -24
- nautobot/docs/administration/nautobot-shell.md +6 -6
- nautobot/docs/administration/replicating-nautobot.md +0 -10
- nautobot/docs/configuration/index.md +9 -9
- nautobot/docs/configuration/optional-settings.md +32 -61
- nautobot/docs/configuration/required-settings.md +11 -52
- nautobot/docs/development/application-registry.md +2 -13
- nautobot/docs/development/best-practices.md +2 -1
- nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
- nautobot/docs/development/extending-models.md +15 -17
- nautobot/docs/development/generic-views.md +0 -2
- nautobot/docs/development/getting-started.md +56 -6
- nautobot/docs/development/navigation-menu.md +22 -93
- nautobot/docs/development/react-ui.md +105 -0
- nautobot/docs/development/release-checklist.md +3 -3
- nautobot/docs/development/role-internals.md +1 -3
- nautobot/docs/development/style-guide.md +6 -4
- nautobot/docs/development/templates.md +2 -1
- nautobot/docs/docker/index.md +16 -14
- nautobot/docs/index.md +7 -3
- nautobot/docs/installation/index.md +4 -1
- nautobot/docs/installation/migrating-from-netbox.md +12 -43
- nautobot/docs/installation/migrating-from-postgresql.md +1 -1
- nautobot/docs/installation/nautobot.md +1 -1
- nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
- nautobot/docs/installation/upgrading.md +5 -2
- nautobot/docs/models/dcim/device.md +3 -0
- nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
- nautobot/docs/models/extras/computedfield.md +4 -4
- nautobot/docs/models/extras/dynamicgroup.md +9 -9
- nautobot/docs/models/extras/gitrepository.md +3 -0
- nautobot/docs/models/extras/job.md +1 -0
- nautobot/docs/models/extras/jobbutton.md +18 -13
- nautobot/docs/models/extras/jobhook.md +7 -4
- nautobot/docs/models/extras/jobresult.md +6 -2
- nautobot/docs/models/extras/relationship.md +2 -2
- nautobot/docs/models/extras/status.md +6 -19
- nautobot/docs/models/ipam/ipaddress.md +3 -0
- nautobot/docs/models/ipam/vrf.md +0 -3
- nautobot/docs/models/virtualization/virtualmachine.md +3 -0
- nautobot/docs/plugins/development.md +92 -24
- nautobot/docs/release-notes/version-1.5.md +96 -0
- nautobot/docs/release-notes/version-2.0.md +216 -0
- nautobot/docs/requirements.txt +5 -4
- nautobot/docs/rest-api/overview.md +384 -215
- nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
- nautobot/extras/admin.py +3 -5
- nautobot/extras/api/customfields.py +15 -39
- nautobot/extras/api/fields.py +0 -11
- nautobot/extras/api/mixins.py +45 -0
- nautobot/extras/api/relationships.py +63 -159
- nautobot/extras/api/serializers.py +165 -706
- nautobot/extras/api/urls.py +1 -1
- nautobot/extras/api/views.py +295 -282
- nautobot/extras/apps.py +4 -7
- nautobot/extras/choices.py +11 -22
- nautobot/extras/constants.py +9 -3
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +135 -186
- nautobot/extras/datasources/registry.py +25 -35
- nautobot/extras/factory.py +1 -3
- nautobot/extras/filters/__init__.py +49 -47
- nautobot/extras/filters/mixins.py +10 -8
- nautobot/extras/forms/forms.py +72 -148
- nautobot/extras/forms/mixins.py +34 -57
- nautobot/extras/health_checks.py +0 -33
- nautobot/extras/jobs.py +387 -566
- nautobot/extras/management/__init__.py +55 -48
- nautobot/extras/management/commands/renaturalize.py +0 -1
- nautobot/extras/management/commands/runjob.py +24 -62
- nautobot/extras/management/commands/webhook_receiver.py +0 -1
- nautobot/extras/managers.py +30 -7
- nautobot/extras/migrations/0001_initial_part_1.py +0 -1
- nautobot/extras/migrations/0002_initial_part_2.py +0 -1
- nautobot/extras/migrations/0003_initial_part_3.py +0 -1
- nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
- nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
- nautobot/extras/migrations/0006_graphqlquery.py +0 -1
- nautobot/extras/migrations/0007_configcontextschema.py +0 -1
- nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
- nautobot/extras/migrations/0009_computedfield.py +0 -1
- nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
- nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
- nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
- nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
- nautobot/extras/migrations/0014_auto_slug.py +0 -1
- nautobot/extras/migrations/0015_scheduled_job.py +0 -1
- nautobot/extras/migrations/0016_secret.py +0 -1
- nautobot/extras/migrations/0017_joblogentry.py +0 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
- nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
- nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
- nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
- nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
- nautobot/extras/migrations/0023_job_model.py +0 -1
- nautobot/extras/migrations/0024_job_data_migration.py +0 -1
- nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
- nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
- nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
- nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
- nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
- nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
- nautobot/extras/migrations/0031_tag_content_types.py +0 -1
- nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
- nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
- nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
- nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
- nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
- nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
- nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
- nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
- nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
- nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
- nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
- nautobot/extras/migrations/0043_note.py +0 -1
- nautobot/extras/migrations/0044_add_job_hook.py +0 -1
- nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
- nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
- nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
- nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
- nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
- nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
- nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
- nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
- nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
- nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
- nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
- nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
- nautobot/extras/migrations/0057_jobbutton.py +0 -1
- nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
- nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
- nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
- nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
- nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
- nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
- nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
- nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
- nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
- nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
- nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
- nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
- nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
- nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
- nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
- nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
- nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
- nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
- nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
- nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
- nautobot/extras/migrations/0078_remove_slug.py +45 -0
- nautobot/extras/migrations/0079_tagsfield.py +28 -0
- nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
- nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
- nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
- nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
- nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
- nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
- nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
- nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
- nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
- nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
- nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
- nautobot/extras/models/__init__.py +2 -3
- nautobot/extras/models/change_logging.py +0 -36
- nautobot/extras/models/customfields.py +39 -33
- nautobot/extras/models/datasources.py +48 -50
- nautobot/extras/models/groups.py +5 -12
- nautobot/extras/models/jobs.py +190 -323
- nautobot/extras/models/mixins.py +0 -71
- nautobot/extras/models/models.py +1 -22
- nautobot/extras/models/relationships.py +20 -21
- nautobot/extras/models/roles.py +0 -23
- nautobot/extras/models/secrets.py +2 -31
- nautobot/extras/models/statuses.py +6 -5
- nautobot/extras/models/tags.py +2 -17
- nautobot/extras/navigation.py +89 -307
- nautobot/extras/plugins/__init__.py +3 -121
- nautobot/extras/plugins/utils.py +0 -3
- nautobot/extras/plugins/validators.py +5 -4
- nautobot/extras/plugins/views.py +16 -4
- nautobot/extras/querysets.py +1 -7
- nautobot/extras/registry.py +3 -0
- nautobot/extras/signals.py +26 -60
- nautobot/extras/tables.py +42 -49
- nautobot/extras/tasks.py +0 -12
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +16 -1
- nautobot/extras/templates/extras/customfield.html +0 -13
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
- nautobot/extras/templates/extras/gitrepository.html +3 -3
- nautobot/extras/templates/extras/inc/jobresult.html +10 -0
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
- nautobot/extras/templates/extras/job.html +35 -25
- nautobot/extras/templates/extras/job_approval_request.html +15 -30
- nautobot/extras/templates/extras/job_detail.html +13 -31
- nautobot/extras/templates/extras/job_edit.html +14 -17
- nautobot/extras/templates/extras/jobresult.html +24 -6
- nautobot/extras/templates/extras/objectchange_list.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +2 -2
- nautobot/extras/templates/extras/secret.html +28 -0
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
- nautobot/extras/templatetags/custom_links.py +0 -2
- nautobot/extras/templatetags/job_buttons.py +1 -0
- nautobot/extras/templatetags/plugins.py +0 -1
- nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
- nautobot/extras/test_jobs/atomic_transaction.py +53 -0
- nautobot/extras/test_jobs/dry_run.py +29 -0
- nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
- nautobot/extras/test_jobs/duplicate_name2.py +9 -0
- nautobot/extras/test_jobs/fail.py +23 -0
- nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
- nautobot/extras/test_jobs/file_upload_pass.py +25 -0
- nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
- nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
- nautobot/extras/test_jobs/job_button_receiver.py +28 -0
- nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
- nautobot/extras/test_jobs/job_variables.py +88 -0
- nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
- nautobot/extras/test_jobs/log_redaction.py +20 -0
- nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
- nautobot/extras/test_jobs/modify_db.py +25 -0
- nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
- nautobot/extras/test_jobs/object_var_optional.py +21 -0
- nautobot/extras/test_jobs/object_var_required.py +21 -0
- nautobot/extras/test_jobs/object_vars.py +26 -0
- nautobot/extras/test_jobs/pass.py +25 -0
- nautobot/extras/test_jobs/profiling.py +32 -0
- nautobot/extras/test_jobs/read_only_job.py +15 -0
- nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
- nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
- nautobot/extras/tests/integration/__init__.py +3 -3
- nautobot/extras/tests/integration/test_computedfields.py +1 -1
- nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
- nautobot/extras/tests/integration/test_customfields.py +4 -2
- nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
- nautobot/extras/tests/integration/test_jobs.py +25 -27
- nautobot/extras/tests/integration/test_notes.py +8 -4
- nautobot/extras/tests/integration/test_plugins.py +4 -4
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_api.py +371 -381
- nautobot/extras/tests/test_changelog.py +17 -16
- nautobot/extras/tests/test_context_managers.py +5 -6
- nautobot/extras/tests/test_customfields.py +112 -73
- nautobot/extras/tests/test_datasources.py +191 -117
- nautobot/extras/tests/test_dynamicgroups.py +45 -68
- nautobot/extras/tests/test_filters.py +170 -130
- nautobot/extras/tests/test_forms.py +107 -109
- nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
- nautobot/extras/tests/test_jobs.py +271 -273
- nautobot/extras/tests/test_management.py +3 -6
- nautobot/extras/tests/test_migrations.py +5 -3
- nautobot/extras/tests/test_models.py +121 -173
- nautobot/extras/tests/test_notes.py +0 -1
- nautobot/extras/tests/test_plugins.py +55 -89
- nautobot/extras/tests/test_relationships.py +174 -130
- nautobot/extras/tests/test_tags.py +6 -12
- nautobot/extras/tests/test_utils.py +31 -1
- nautobot/extras/tests/test_views.py +223 -184
- nautobot/extras/tests/test_webhooks.py +16 -15
- nautobot/extras/urls.py +69 -69
- nautobot/extras/utils.py +137 -163
- nautobot/extras/views.py +81 -153
- nautobot/ipam/api/fields.py +17 -0
- nautobot/ipam/api/serializers.py +77 -164
- nautobot/ipam/api/urls.py +4 -1
- nautobot/ipam/api/views.py +28 -19
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/choices.py +5 -12
- nautobot/ipam/constants.py +1 -0
- nautobot/ipam/factory.py +41 -30
- nautobot/ipam/filters.py +58 -25
- nautobot/ipam/forms.py +82 -211
- nautobot/ipam/graphql/types.py +0 -9
- nautobot/ipam/lookups.py +13 -8
- nautobot/ipam/management/commands/__init__.py +0 -0
- nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
- nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
- nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
- nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
- nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
- nautobot/ipam/migrations/0005_auto_slug.py +0 -1
- nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
- nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
- nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
- nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
- nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
- nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
- nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
- nautobot/ipam/migrations/0013_delete_role.py +0 -1
- nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
- nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
- nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
- nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
- nautobot/ipam/migrations/0019_created_datetime.py +0 -1
- nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
- nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
- nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
- nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
- nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
- nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
- nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
- nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
- nautobot/ipam/migrations/0028_tagsfield.py +44 -0
- nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
- nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
- nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
- nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
- nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
- nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
- nautobot/ipam/models.py +579 -368
- nautobot/ipam/navigation.py +36 -159
- nautobot/ipam/querysets.py +117 -90
- nautobot/ipam/signals.py +89 -0
- nautobot/ipam/tables.py +86 -28
- nautobot/ipam/templates/ipam/ipaddress.html +14 -30
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
- nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
- nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
- nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
- nautobot/ipam/templates/ipam/prefix.html +27 -33
- nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
- nautobot/ipam/templates/ipam/vlangroup.html +0 -13
- nautobot/ipam/templates/ipam/vrf.html +6 -4
- nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
- nautobot/ipam/tests/integration/test_prefixes.py +4 -27
- nautobot/ipam/tests/test_api.py +60 -61
- nautobot/ipam/tests/test_filters.py +187 -126
- nautobot/ipam/tests/test_forms.py +12 -6
- nautobot/ipam/tests/test_graphql.py +8 -6
- nautobot/ipam/tests/test_migrations.py +8 -13
- nautobot/ipam/tests/test_models.py +426 -274
- nautobot/ipam/tests/test_ordering.py +6 -3
- nautobot/ipam/tests/test_querysets.py +340 -96
- nautobot/ipam/tests/test_views.py +100 -55
- nautobot/ipam/urls.py +28 -5
- nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
- nautobot/ipam/utils/migrations.py +713 -0
- nautobot/ipam/views.py +237 -122
- nautobot/project-static/docs/404.html +1399 -166
- nautobot/project-static/docs/additional-features/caching.html +1416 -320
- nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
- nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
- nautobot/project-static/docs/additional-features/graphql.html +1389 -190
- nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
- nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
- nautobot/project-static/docs/additional-features/jobs.html +1677 -460
- nautobot/project-static/docs/additional-features/napalm.html +1389 -190
- nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
- nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
- nautobot/project-static/docs/administration/celery-queues.html +1389 -190
- nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
- nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
- nautobot/project-static/docs/administration/permissions.html +1389 -190
- nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
- nautobot/project-static/docs/apps/index.html +1389 -190
- nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
- nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
- nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
- nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
- nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
- nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
- nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
- nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
- nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
- nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
- nautobot/project-static/docs/configuration/index.html +1398 -199
- nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
- nautobot/project-static/docs/configuration/required-settings.html +1419 -287
- nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
- nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
- nautobot/project-static/docs/core-functionality/devices.html +1452 -249
- nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
- nautobot/project-static/docs/core-functionality/power.html +1448 -249
- nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
- nautobot/project-static/docs/core-functionality/services.html +1448 -249
- nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
- nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
- nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
- nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
- nautobot/project-static/docs/development/application-registry.html +1393 -214
- nautobot/project-static/docs/development/best-practices.html +1392 -192
- nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
- nautobot/project-static/docs/development/extending-models.html +1443 -257
- nautobot/project-static/docs/development/generic-views.html +1403 -174
- nautobot/project-static/docs/development/getting-started.html +1568 -262
- nautobot/project-static/docs/development/homepage.html +1389 -190
- nautobot/project-static/docs/development/index.html +1389 -190
- nautobot/project-static/docs/development/model-features.html +1389 -190
- nautobot/project-static/docs/development/natural-keys.html +1389 -190
- nautobot/project-static/docs/development/navigation-menu.html +1451 -330
- nautobot/project-static/docs/development/react-ui.html +4199 -0
- nautobot/project-static/docs/development/release-checklist.html +1392 -193
- nautobot/project-static/docs/development/role-internals.html +1402 -172
- nautobot/project-static/docs/development/style-guide.html +1399 -199
- nautobot/project-static/docs/development/templates.html +1391 -191
- nautobot/project-static/docs/development/testing.html +1389 -190
- nautobot/project-static/docs/development/user-preferences.html +1389 -190
- nautobot/project-static/docs/docker/index.html +1408 -206
- nautobot/project-static/docs/index.html +1397 -180
- nautobot/project-static/docs/installation/centos.html +1401 -170
- nautobot/project-static/docs/installation/external-authentication.html +1389 -190
- nautobot/project-static/docs/installation/http-server.html +1389 -190
- nautobot/project-static/docs/installation/index.html +1394 -191
- nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
- nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
- nautobot/project-static/docs/installation/nautobot.html +1390 -191
- nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
- nautobot/project-static/docs/installation/services.html +1389 -190
- nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/project-static/docs/installation/ubuntu.html +1401 -170
- nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
- nautobot/project-static/docs/installation/upgrading.html +1395 -192
- nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
- nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
- nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
- nautobot/project-static/docs/models/circuits/provider.html +1427 -174
- nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
- nautobot/project-static/docs/models/dcim/cable.html +1458 -174
- nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
- nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/device.html +1431 -174
- nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
- nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
- nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
- nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
- nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/interface.html +1427 -174
- nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
- nautobot/project-static/docs/models/dcim/location.html +1427 -174
- nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
- nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
- nautobot/project-static/docs/models/dcim/platform.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
- nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
- nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
- nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
- nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/rack.html +1427 -174
- nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
- nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
- nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
- nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
- nautobot/project-static/docs/models/dcim/region.html +1401 -170
- nautobot/project-static/docs/models/dcim/site.html +1401 -170
- nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
- nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
- nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
- nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
- nautobot/project-static/docs/models/extras/customfield.html +1389 -190
- nautobot/project-static/docs/models/extras/customlink.html +1389 -190
- nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
- nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
- nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
- nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
- nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
- nautobot/project-static/docs/models/extras/job.html +1411 -157
- nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
- nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
- nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
- nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
- nautobot/project-static/docs/models/extras/note.html +1389 -190
- nautobot/project-static/docs/models/extras/relationship.html +1391 -192
- nautobot/project-static/docs/models/extras/role.html +1495 -198
- nautobot/project-static/docs/models/extras/secret.html +1492 -201
- nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
- nautobot/project-static/docs/models/extras/status.html +1381 -221
- nautobot/project-static/docs/models/extras/tag.html +1389 -190
- nautobot/project-static/docs/models/extras/webhook.html +1389 -190
- nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
- nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
- nautobot/project-static/docs/models/ipam/rir.html +1410 -157
- nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
- nautobot/project-static/docs/models/ipam/service.html +1410 -157
- nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
- nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
- nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
- nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
- nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
- nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
- nautobot/project-static/docs/models/users/token.html +1410 -157
- nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
- nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
- nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
- nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
- nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/plugins/development.html +1916 -646
- nautobot/project-static/docs/plugins/index.html +1389 -190
- nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
- nautobot/project-static/docs/release-notes/index.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
- nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
- nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
- nautobot/project-static/docs/requirements.txt +5 -4
- nautobot/project-static/docs/rest-api/authentication.html +1389 -190
- nautobot/project-static/docs/rest-api/filtering.html +1389 -190
- nautobot/project-static/docs/rest-api/overview.html +2002 -576
- nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +197 -187
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
- nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
- nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
- nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
- nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
- nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
- nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
- nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
- nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
- nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
- nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
- nautobot/project-static/docs/user-guides/graphql.html +1402 -203
- nautobot/project-static/docs/user-guides/relationships.html +1448 -249
- nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
- nautobot/project-static/js/forms.js +16 -9
- nautobot/project-static/js/theme.js +5 -0
- nautobot/tenancy/api/serializers.py +4 -34
- nautobot/tenancy/api/urls.py +1 -1
- nautobot/tenancy/filters/__init__.py +9 -7
- nautobot/tenancy/filters/mixins.py +3 -2
- nautobot/tenancy/forms.py +3 -36
- nautobot/tenancy/migrations/0001_initial.py +0 -1
- nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
- nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
- nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
- nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
- nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
- nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
- nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
- nautobot/tenancy/models.py +0 -30
- nautobot/tenancy/navigation.py +6 -39
- nautobot/tenancy/tables.py +4 -4
- nautobot/tenancy/templates/tenancy/tenant.html +12 -12
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/tests/test_api.py +1 -12
- nautobot/tenancy/tests/test_filters.py +20 -12
- nautobot/tenancy/tests/test_views.py +11 -29
- nautobot/tenancy/urls.py +10 -10
- nautobot/tenancy/views.py +0 -3
- nautobot/ui/.eslintignore +6 -0
- nautobot/ui/.gitignore +10 -0
- nautobot/ui/.prettierignore +9 -0
- nautobot/ui/.prettierrc +4 -0
- nautobot/ui/README.md +33 -0
- nautobot/ui/app_imports.js.j2 +7 -0
- nautobot/ui/craco.config.js +46 -0
- nautobot/ui/jsconfig-base.json +11 -0
- nautobot/ui/jsconfig.json +5 -0
- nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
- nautobot/ui/package-lock.json +21451 -0
- nautobot/ui/package.json +70 -0
- nautobot/ui/public/index.html +47 -0
- nautobot/ui/public/logo192.png +0 -0
- nautobot/ui/public/logo512.png +0 -0
- nautobot/ui/public/manifest.json +25 -0
- nautobot/ui/public/nautobot_logo.svg +131 -0
- nautobot/ui/public/robots.txt +3 -0
- nautobot/ui/src/App.js +71 -0
- nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
- nautobot/ui/src/components/AppTab.js +40 -0
- nautobot/ui/src/components/Apps.js +60 -0
- nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
- nautobot/ui/src/components/HomePanel.js +58 -0
- nautobot/ui/src/components/JobHistoryTable.js +78 -0
- nautobot/ui/src/components/Layout.js +53 -0
- nautobot/ui/src/components/LoadingWidget.js +25 -0
- nautobot/ui/src/components/Navbar.js +116 -0
- nautobot/ui/src/components/NotificationPopover.js +27 -0
- nautobot/ui/src/components/ObjectListTable.js +209 -0
- nautobot/ui/src/components/ReferenceDataTag.js +35 -0
- nautobot/ui/src/components/RouterButton.js +10 -0
- nautobot/ui/src/components/RouterLink.js +10 -0
- nautobot/ui/src/components/SidebarNav.js +147 -0
- nautobot/ui/src/components/Table.js +48 -0
- nautobot/ui/src/components/TableItem.js +71 -0
- nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
- nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
- nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
- nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
- nautobot/ui/src/components/__tests__/Table.test.js +36 -0
- nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
- nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
- nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
- nautobot/ui/src/components/pagination.js +93 -0
- nautobot/ui/src/components/paginator.js +79 -0
- nautobot/ui/src/components/paginator_form.js +43 -0
- nautobot/ui/src/components/usePagination.js +57 -0
- nautobot/ui/src/constants/apiPath.js +10 -0
- nautobot/ui/src/constants/icons.js +15 -0
- nautobot/ui/src/constants/size.js +15 -0
- nautobot/ui/src/index.js +65 -0
- nautobot/ui/src/reportWebVitals.js +15 -0
- nautobot/ui/src/router.js +77 -0
- nautobot/ui/src/utils/api.js +131 -0
- nautobot/ui/src/utils/app-import.js +15 -0
- nautobot/ui/src/utils/color.js +15 -0
- nautobot/ui/src/utils/date.js +14 -0
- nautobot/ui/src/utils/index.js +15 -0
- nautobot/ui/src/utils/navigation.js +32 -0
- nautobot/ui/src/utils/session.js +64 -0
- nautobot/ui/src/utils/store.js +242 -0
- nautobot/ui/src/utils/string.js +6 -0
- nautobot/ui/src/utils/url.js +4 -0
- nautobot/ui/src/views/Home.js +138 -0
- nautobot/ui/src/views/InstalledApps.js +80 -0
- nautobot/ui/src/views/Login.js +48 -0
- nautobot/ui/src/views/Logout.js +20 -0
- nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
- nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
- nautobot/ui/src/views/__tests__/Login.test.js +15 -0
- nautobot/ui/src/views/generic/GenericView.js +142 -0
- nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
- nautobot/ui/src/views/generic/ObjectList.js +127 -0
- nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +51 -61
- nautobot/users/api/urls.py +1 -1
- nautobot/users/api/views.py +53 -2
- nautobot/users/migrations/0001_initial.py +0 -1
- nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
- nautobot/users/migrations/0003_alter_user_options.py +0 -1
- nautobot/users/migrations/0004_alter_user_managers.py +0 -1
- nautobot/users/tests/test_api.py +109 -28
- nautobot/users/tests/test_filters.py +0 -4
- nautobot/users/tests/test_models.py +0 -1
- nautobot/users/views.py +0 -7
- nautobot/virtualization/api/serializers.py +18 -132
- nautobot/virtualization/api/urls.py +1 -1
- nautobot/virtualization/api/views.py +1 -22
- nautobot/virtualization/choices.py +0 -2
- nautobot/virtualization/filters.py +12 -7
- nautobot/virtualization/forms.py +21 -117
- nautobot/virtualization/migrations/0001_initial.py +0 -1
- nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
- nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
- nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
- nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
- nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
- nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
- nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
- nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
- nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
- nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
- nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
- nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
- nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
- nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
- nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
- nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
- nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
- nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
- nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
- nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
- nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
- nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
- nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
- nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
- nautobot/virtualization/models.py +39 -131
- nautobot/virtualization/navigation.py +18 -99
- nautobot/virtualization/tables.py +4 -4
- nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
- nautobot/virtualization/tests/test_api.py +42 -52
- nautobot/virtualization/tests/test_filters.py +98 -75
- nautobot/virtualization/tests/test_models.py +36 -13
- nautobot/virtualization/tests/test_views.py +68 -73
- nautobot/virtualization/urls.py +10 -10
- nautobot/virtualization/views.py +8 -14
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
- nautobot/circuits/api/nested_serializers.py +0 -69
- nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
- nautobot/dcim/api/nested_serializers.py +0 -356
- nautobot/dcim/templates/dcim/device_import.html +0 -5
- nautobot/dcim/templates/dcim/device_import_child.html +0 -5
- nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
- nautobot/extras/api/nested_serializers.py +0 -353
- nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
- nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
- nautobot/extras/reports.py +0 -60
- nautobot/extras/scripts.py +0 -72
- nautobot/extras/tests/example_jobs/script_variables.py +0 -67
- nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
- nautobot/extras/tests/example_jobs/test_fail.py +0 -16
- nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
- nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
- nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
- nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
- nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
- nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
- nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
- nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
- nautobot/extras/tests/example_jobs/test_pass.py +0 -19
- nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
- nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
- nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
- nautobot/ipam/api/nested_serializers.py +0 -143
- nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
- nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
- nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
- nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
- nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
- nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
- nautobot/tenancy/api/nested_serializers.py +0 -31
- nautobot/users/api/nested_serializers.py +0 -67
- nautobot/virtualization/api/nested_serializers.py +0 -65
- /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
- /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
from io import StringIO
|
|
2
3
|
import json
|
|
3
|
-
import
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
import re
|
|
5
|
-
import uuid
|
|
6
|
-
from io import StringIO
|
|
7
6
|
from unittest import mock
|
|
7
|
+
import uuid
|
|
8
8
|
|
|
9
|
-
from django.contrib.auth import get_user_model
|
|
10
9
|
from django.contrib.contenttypes.models import ContentType
|
|
11
|
-
from django.core.exceptions import ObjectDoesNotExist
|
|
12
10
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
13
11
|
from django.core.management import call_command
|
|
14
12
|
from django.core.management.base import CommandError
|
|
@@ -19,7 +17,8 @@ from django.utils import timezone
|
|
|
19
17
|
from nautobot.core.testing import (
|
|
20
18
|
TestCase,
|
|
21
19
|
TransactionTestCase,
|
|
22
|
-
|
|
20
|
+
create_job_result_and_run_job,
|
|
21
|
+
get_job_class_and_model,
|
|
23
22
|
)
|
|
24
23
|
from nautobot.core.utils.lookup import get_changes_for_model
|
|
25
24
|
from nautobot.dcim.models import Device, Location, LocationType
|
|
@@ -30,31 +29,11 @@ from nautobot.extras.choices import (
|
|
|
30
29
|
ObjectChangeEventContextChoices,
|
|
31
30
|
)
|
|
32
31
|
from nautobot.extras.context_managers import JobHookChangeContext, change_logging, web_request_context
|
|
33
|
-
from nautobot.extras.jobs import get_job
|
|
34
|
-
from nautobot.extras.models import CustomField, FileProxy,
|
|
32
|
+
from nautobot.extras.jobs import get_job
|
|
33
|
+
from nautobot.extras.models import CustomField, FileProxy, JobHook, JobResult, Role, ScheduledJob, Status
|
|
35
34
|
from nautobot.extras.models.models import JobLogEntry
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
def get_job_class_and_model(module, name):
|
|
39
|
-
"""Test helper function to look up a job class and job model and ensure the latter is enabled."""
|
|
40
|
-
class_path = f"local/{module}/{name}"
|
|
41
|
-
job_class = get_job(class_path)
|
|
42
|
-
job_model = Job.objects.get_for_class_path(class_path)
|
|
43
|
-
job_model.enabled = True
|
|
44
|
-
job_model.validated_save()
|
|
45
|
-
return (job_class, job_model)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def create_job_result_and_run_job(module, name, *, data=None, commit=True, request=None):
|
|
49
|
-
"""Test helper function to call get_job_class_and_model() then and call run_job_for_testing()."""
|
|
50
|
-
if data is None:
|
|
51
|
-
data = {}
|
|
52
|
-
_job_class, job_model = get_job_class_and_model(module, name)
|
|
53
|
-
job_result = run_job_for_testing(job=job_model, data=data, commit=commit, request=request)
|
|
54
|
-
job_result.refresh_from_db()
|
|
55
|
-
return job_result
|
|
56
|
-
|
|
57
|
-
|
|
58
37
|
class JobTest(TransactionTestCase):
|
|
59
38
|
"""
|
|
60
39
|
Test basic jobs to ensure importing works.
|
|
@@ -73,11 +52,11 @@ class JobTest(TransactionTestCase):
|
|
|
73
52
|
|
|
74
53
|
def test_job_hard_time_limit_less_than_soft_time_limit(self):
|
|
75
54
|
"""
|
|
76
|
-
Job test which produces a
|
|
55
|
+
Job test which produces a warning log message because the time_limit is less than the soft_time_limit.
|
|
77
56
|
"""
|
|
78
|
-
module = "
|
|
57
|
+
module = "soft_time_limit_greater_than_time_limit"
|
|
79
58
|
name = "TestSoftTimeLimitGreaterThanHardTimeLimit"
|
|
80
|
-
job_result = create_job_result_and_run_job(module, name
|
|
59
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
81
60
|
log_warning = JobLogEntry.objects.filter(
|
|
82
61
|
job_result=job_result, log_level=LogLevelChoices.LOG_WARNING, grouping="initialization"
|
|
83
62
|
).first()
|
|
@@ -88,48 +67,34 @@ class JobTest(TransactionTestCase):
|
|
|
88
67
|
"This job will fail silently after 5.0 seconds.",
|
|
89
68
|
)
|
|
90
69
|
|
|
91
|
-
def
|
|
70
|
+
def test_job_pass(self):
|
|
92
71
|
"""
|
|
93
|
-
Job test with pass result
|
|
94
|
-
|
|
95
|
-
Because calling run_job directly used to be the best practice for testing jobs, we want to ensure that calling
|
|
96
|
-
it still works even if we ever change the run_job call in the run_job_for_testing wrapper.
|
|
72
|
+
Job test with pass result.
|
|
97
73
|
"""
|
|
98
|
-
module = "
|
|
74
|
+
module = "pass"
|
|
99
75
|
name = "TestPass"
|
|
100
|
-
|
|
101
|
-
job_model.enabled = True
|
|
102
|
-
job_model.validated_save()
|
|
103
|
-
job_content_type = ContentType.objects.get(app_label="extras", model="job")
|
|
104
|
-
job_result = JobResult.objects.create(
|
|
105
|
-
name=job_model.class_path,
|
|
106
|
-
obj_type=job_content_type,
|
|
107
|
-
job_model=job_model,
|
|
108
|
-
user=None,
|
|
109
|
-
task_id=uuid.uuid4(),
|
|
110
|
-
)
|
|
111
|
-
run_job(data={}, request=None, commit=False, job_result_pk=job_result.pk)
|
|
112
|
-
job_result = create_job_result_and_run_job(module, name, commit=False)
|
|
76
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
113
77
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
114
78
|
|
|
115
|
-
def
|
|
79
|
+
def test_job_result_manager_censor_sensitive_variables(self):
|
|
116
80
|
"""
|
|
117
|
-
Job test with
|
|
81
|
+
Job test with JobResult Censored Sensitive Variables.
|
|
118
82
|
"""
|
|
119
|
-
module = "
|
|
120
|
-
name = "
|
|
121
|
-
|
|
83
|
+
module = "has_sensitive_variables"
|
|
84
|
+
name = "TestHasSensitiveVariables"
|
|
85
|
+
# This function create_job_result_and_run_job and the subsequent functions' arguments are very messy
|
|
86
|
+
job_result = create_job_result_and_run_job(module, name, "local", 1, 2, "3", kwarg_1=1, kwarg_2="2")
|
|
122
87
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
88
|
+
self.assertEqual(job_result.task_args, [])
|
|
89
|
+
self.assertEqual(job_result.task_kwargs, {})
|
|
123
90
|
|
|
124
91
|
def test_job_fail(self):
|
|
125
92
|
"""
|
|
126
93
|
Job test with fail result.
|
|
127
94
|
"""
|
|
128
|
-
module = "
|
|
95
|
+
module = "fail"
|
|
129
96
|
name = "TestFail"
|
|
130
|
-
|
|
131
|
-
job_result = create_job_result_and_run_job(module, name, commit=False)
|
|
132
|
-
logging.disable(logging.NOTSET)
|
|
97
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
133
98
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
134
99
|
|
|
135
100
|
def test_field_default(self):
|
|
@@ -137,9 +102,9 @@ class JobTest(TransactionTestCase):
|
|
|
137
102
|
Job test with field that is a default value that is falsey.
|
|
138
103
|
https://github.com/nautobot/nautobot/issues/2039
|
|
139
104
|
"""
|
|
140
|
-
module = "
|
|
105
|
+
module = "field_default"
|
|
141
106
|
name = "TestFieldDefault"
|
|
142
|
-
job_class = get_job(f"
|
|
107
|
+
job_class = get_job(f"{module}.{name}")
|
|
143
108
|
form = job_class().as_form()
|
|
144
109
|
|
|
145
110
|
self.assertInHTML(
|
|
@@ -156,75 +121,106 @@ class JobTest(TransactionTestCase):
|
|
|
156
121
|
"""
|
|
157
122
|
Job test with field order.
|
|
158
123
|
"""
|
|
159
|
-
module = "
|
|
124
|
+
module = "field_order"
|
|
160
125
|
name = "TestFieldOrder"
|
|
161
|
-
job_class = get_job(f"
|
|
126
|
+
job_class = get_job(f"{module}.{name}")
|
|
162
127
|
form = job_class().as_form()
|
|
163
|
-
self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_task_queue", "
|
|
128
|
+
self.assertSequenceEqual(list(form.fields.keys()), ["var1", "var2", "var23", "_task_queue", "_profile"])
|
|
164
129
|
|
|
165
130
|
def test_no_field_order(self):
|
|
166
131
|
"""
|
|
167
132
|
Job test without field_order.
|
|
168
133
|
"""
|
|
169
|
-
module = "
|
|
134
|
+
module = "no_field_order"
|
|
170
135
|
name = "TestNoFieldOrder"
|
|
171
|
-
job_class = get_job(f"
|
|
136
|
+
job_class = get_job(f"{module}.{name}")
|
|
172
137
|
form = job_class().as_form()
|
|
173
|
-
self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_task_queue", "
|
|
138
|
+
self.assertSequenceEqual(list(form.fields.keys()), ["var23", "var2", "_task_queue", "_profile"])
|
|
174
139
|
|
|
175
140
|
def test_no_field_order_inherited_variable(self):
|
|
176
141
|
"""
|
|
177
142
|
Job test without field_order with a variable inherited from the base class
|
|
178
143
|
"""
|
|
179
|
-
module = "
|
|
144
|
+
module = "no_field_order"
|
|
180
145
|
name = "TestDefaultFieldOrderWithInheritance"
|
|
181
|
-
job_class = get_job(f"
|
|
146
|
+
job_class = get_job(f"{module}.{name}")
|
|
182
147
|
form = job_class().as_form()
|
|
183
148
|
self.assertSequenceEqual(
|
|
184
149
|
list(form.fields.keys()),
|
|
185
|
-
["testvar1", "b_testvar2", "a_testvar3", "_task_queue", "
|
|
150
|
+
["testvar1", "b_testvar2", "a_testvar3", "_task_queue", "_profile"],
|
|
186
151
|
)
|
|
187
152
|
|
|
188
|
-
def
|
|
153
|
+
def test_atomic_transaction_decorator_job_pass(self):
|
|
189
154
|
"""
|
|
190
|
-
Job
|
|
155
|
+
Job with @transaction.atomic decorator test with pass result.
|
|
191
156
|
"""
|
|
192
|
-
module = "
|
|
193
|
-
name = "
|
|
194
|
-
job_result = create_job_result_and_run_job(module, name
|
|
157
|
+
module = "atomic_transaction"
|
|
158
|
+
name = "TestAtomicDecorator"
|
|
159
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
195
160
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
""
|
|
200
|
-
|
|
201
|
-
""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
161
|
+
# Ensure DB transaction was not aborted
|
|
162
|
+
self.assertTrue(Status.objects.filter(name="Test database atomic rollback 1").exists())
|
|
163
|
+
# Ensure the correct job log messages were saved
|
|
164
|
+
job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
|
|
165
|
+
self.assertEqual(len(job_logs), 3)
|
|
166
|
+
self.assertIn("Running job", job_logs)
|
|
167
|
+
self.assertIn("Job succeeded.", job_logs)
|
|
168
|
+
self.assertIn("Job completed", job_logs)
|
|
169
|
+
self.assertNotIn("Job failed, all database changes have been rolled back.", job_logs)
|
|
170
|
+
|
|
171
|
+
def test_atomic_transaction_context_manager_job_pass(self):
|
|
172
|
+
"""
|
|
173
|
+
Job with `with transaction.atomic()` context manager test with pass result.
|
|
174
|
+
"""
|
|
175
|
+
module = "atomic_transaction"
|
|
176
|
+
name = "TestAtomicContextManager"
|
|
177
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
178
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
179
|
+
# Ensure DB transaction was not aborted
|
|
180
|
+
self.assertTrue(Status.objects.filter(name="Test database atomic rollback 2").exists())
|
|
181
|
+
# Ensure the correct job log messages were saved
|
|
182
|
+
job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
|
|
183
|
+
self.assertEqual(len(job_logs), 3)
|
|
184
|
+
self.assertIn("Running job", job_logs)
|
|
185
|
+
self.assertIn("Job succeeded.", job_logs)
|
|
186
|
+
self.assertIn("Job completed", job_logs)
|
|
187
|
+
self.assertNotIn("Job failed, all database changes have been rolled back.", job_logs)
|
|
188
|
+
|
|
189
|
+
def test_atomic_transaction_decorator_job_fail(self):
|
|
190
|
+
"""
|
|
191
|
+
Job with @transaction.atomic decorator test with fail result.
|
|
192
|
+
"""
|
|
193
|
+
module = "atomic_transaction"
|
|
194
|
+
name = "TestAtomicDecorator"
|
|
195
|
+
job_result = create_job_result_and_run_job(module, name, fail=True)
|
|
207
196
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
""
|
|
216
|
-
Job
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
197
|
+
# Ensure DB transaction was aborted
|
|
198
|
+
self.assertFalse(Status.objects.filter(name="Test database atomic rollback 1").exists())
|
|
199
|
+
# Ensure the correct job log messages were saved
|
|
200
|
+
job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
|
|
201
|
+
self.assertEqual(len(job_logs), 3)
|
|
202
|
+
self.assertIn("Running job", job_logs)
|
|
203
|
+
self.assertIn("Job failed, all database changes have been rolled back.", job_logs)
|
|
204
|
+
self.assertIn("Job completed", job_logs)
|
|
205
|
+
self.assertNotIn("Job succeeded.", job_logs)
|
|
206
|
+
|
|
207
|
+
def test_atomic_transaction_context_manager_job_fail(self):
|
|
208
|
+
"""
|
|
209
|
+
Job with `with transaction.atomic()` context manager test with fail result.
|
|
210
|
+
"""
|
|
211
|
+
module = "atomic_transaction"
|
|
212
|
+
name = "TestAtomicContextManager"
|
|
213
|
+
job_result = create_job_result_and_run_job(module, name, fail=True)
|
|
214
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
215
|
+
# Ensure DB transaction was aborted
|
|
216
|
+
self.assertFalse(Status.objects.filter(name="Test database atomic rollback 2").exists())
|
|
217
|
+
# Ensure the correct job log messages were saved
|
|
218
|
+
job_logs = JobLogEntry.objects.filter(job_result=job_result).values_list("message", flat=True)
|
|
219
|
+
self.assertEqual(len(job_logs), 3)
|
|
220
|
+
self.assertIn("Running job", job_logs)
|
|
221
|
+
self.assertIn("Job failed, all database changes have been rolled back.", job_logs)
|
|
222
|
+
self.assertIn("Job completed", job_logs)
|
|
223
|
+
self.assertNotIn("Job succeeded.", job_logs)
|
|
228
224
|
|
|
229
225
|
def test_ip_address_vars(self):
|
|
230
226
|
"""
|
|
@@ -236,26 +232,26 @@ class JobTest(TransactionTestCase):
|
|
|
236
232
|
- IPAddressWithMaskVar
|
|
237
233
|
- IPNetworkVar
|
|
238
234
|
"""
|
|
239
|
-
module = "
|
|
235
|
+
module = "ipaddress_vars"
|
|
240
236
|
name = "TestIPAddresses"
|
|
241
237
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
242
238
|
|
|
243
239
|
# Fill out the form
|
|
244
|
-
form_data =
|
|
245
|
-
ipv4_address
|
|
246
|
-
ipv4_with_mask
|
|
247
|
-
ipv4_network
|
|
248
|
-
ipv6_address
|
|
249
|
-
ipv6_with_mask
|
|
250
|
-
ipv6_network
|
|
251
|
-
|
|
240
|
+
form_data = {
|
|
241
|
+
"ipv4_address": "1.2.3.4",
|
|
242
|
+
"ipv4_with_mask": "1.2.3.4/32",
|
|
243
|
+
"ipv4_network": "1.2.3.0/24",
|
|
244
|
+
"ipv6_address": "2001:db8::1",
|
|
245
|
+
"ipv6_with_mask": "2001:db8::1/64",
|
|
246
|
+
"ipv6_network": "2001:db8::/64",
|
|
247
|
+
}
|
|
252
248
|
form = job_class().as_form(form_data)
|
|
253
249
|
self.assertTrue(form.is_valid())
|
|
254
250
|
|
|
255
251
|
# Prepare the job data
|
|
256
252
|
data = job_class.serialize_data(form.cleaned_data)
|
|
257
253
|
# Need to pass a mock request object as execute_webhooks will be called with the creation of the objects.
|
|
258
|
-
job_result = create_job_result_and_run_job(module, name, data
|
|
254
|
+
job_result = create_job_result_and_run_job(module, name, **data)
|
|
259
255
|
|
|
260
256
|
log_info = JobLogEntry.objects.filter(
|
|
261
257
|
job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
|
|
@@ -274,20 +270,34 @@ class JobTest(TransactionTestCase):
|
|
|
274
270
|
"""
|
|
275
271
|
Test that an attempt is made at log redaction.
|
|
276
272
|
"""
|
|
277
|
-
module = "
|
|
273
|
+
module = "log_redaction"
|
|
278
274
|
name = "TestLogRedaction"
|
|
279
|
-
job_result = create_job_result_and_run_job(module, name
|
|
275
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
280
276
|
|
|
281
277
|
logs = JobLogEntry.objects.filter(job_result=job_result, grouping="run")
|
|
282
278
|
self.assertGreater(logs.count(), 0)
|
|
283
279
|
for log in logs:
|
|
284
|
-
|
|
280
|
+
if log.message != "Job completed":
|
|
281
|
+
self.assertEqual(log.message, "The secret is (redacted)")
|
|
282
|
+
|
|
283
|
+
def test_log_skip_db_logging(self):
|
|
284
|
+
"""
|
|
285
|
+
Test that an attempt is made at log redaction.
|
|
286
|
+
"""
|
|
287
|
+
module = "log_skip_db_logging"
|
|
288
|
+
name = "TestLogSkipDBLogging"
|
|
289
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
290
|
+
|
|
291
|
+
logs = job_result.job_log_entries
|
|
292
|
+
self.assertGreater(logs.count(), 0)
|
|
293
|
+
self.assertFalse(logs.filter(message="I should NOT be logged to the database").exists())
|
|
294
|
+
self.assertTrue(logs.filter(message="I should be logged to the database").exists())
|
|
285
295
|
|
|
286
296
|
def test_object_vars(self):
|
|
287
297
|
"""
|
|
288
298
|
Test that Object variable fields behave as expected.
|
|
289
299
|
"""
|
|
290
|
-
module = "
|
|
300
|
+
module = "object_vars"
|
|
291
301
|
name = "TestObjectVars"
|
|
292
302
|
|
|
293
303
|
# Prepare the job data
|
|
@@ -298,10 +308,7 @@ class JobTest(TransactionTestCase):
|
|
|
298
308
|
"role": {"name": role.name},
|
|
299
309
|
"roles": [role.pk],
|
|
300
310
|
}
|
|
301
|
-
job_result = create_job_result_and_run_job(module, name, data
|
|
302
|
-
|
|
303
|
-
# Test storing additional data in job
|
|
304
|
-
job_result_data = job_result.data["object_vars"]
|
|
311
|
+
job_result = create_job_result_and_run_job(module, name, **data)
|
|
305
312
|
|
|
306
313
|
info_log = JobLogEntry.objects.filter(
|
|
307
314
|
job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
|
|
@@ -309,19 +316,18 @@ class JobTest(TransactionTestCase):
|
|
|
309
316
|
|
|
310
317
|
# Assert stuff
|
|
311
318
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
312
|
-
self.assertEqual({"role": str(role.pk), "roles": [str(role.pk)]}, job_result_data)
|
|
313
319
|
self.assertEqual(info_log.log_object, "")
|
|
314
320
|
self.assertEqual(info_log.message, f"Role: {role.name}")
|
|
315
|
-
self.assertEqual(job_result.
|
|
321
|
+
self.assertEqual(job_result.result, "Nice Roles!")
|
|
316
322
|
|
|
317
323
|
def test_optional_object_var(self):
|
|
318
324
|
"""
|
|
319
325
|
Test that an optional Object variable field behaves as expected.
|
|
320
326
|
"""
|
|
321
|
-
module = "
|
|
327
|
+
module = "object_var_optional"
|
|
322
328
|
name = "TestOptionalObjectVar"
|
|
323
329
|
data = {"location": None}
|
|
324
|
-
job_result = create_job_result_and_run_job(module, name, data
|
|
330
|
+
job_result = create_job_result_and_run_job(module, name, **data)
|
|
325
331
|
|
|
326
332
|
info_log = JobLogEntry.objects.filter(
|
|
327
333
|
job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run"
|
|
@@ -331,52 +337,30 @@ class JobTest(TransactionTestCase):
|
|
|
331
337
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
332
338
|
self.assertEqual(info_log.log_object, "")
|
|
333
339
|
self.assertEqual(info_log.message, "The Location if any that the user provided.")
|
|
334
|
-
self.assertEqual(job_result.
|
|
340
|
+
self.assertEqual(job_result.result, "Nice Location (or not)!")
|
|
335
341
|
|
|
336
342
|
def test_required_object_var(self):
|
|
337
343
|
"""
|
|
338
344
|
Test that a required Object variable field behaves as expected.
|
|
339
345
|
"""
|
|
340
|
-
module = "
|
|
346
|
+
module = "object_var_required"
|
|
341
347
|
name = "TestRequiredObjectVar"
|
|
342
348
|
data = {"location": None}
|
|
343
|
-
|
|
344
|
-
job_result = create_job_result_and_run_job(module, name, data=data, commit=False)
|
|
345
|
-
logging.disable(logging.NOTSET)
|
|
346
|
-
|
|
347
|
-
# Assert stuff
|
|
348
|
-
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
349
|
-
log_failure = JobLogEntry.objects.filter(
|
|
350
|
-
grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE
|
|
351
|
-
).first()
|
|
352
|
-
self.assertIn("location is a required field", log_failure.message)
|
|
349
|
+
job_result = create_job_result_and_run_job(module, name, **data)
|
|
353
350
|
|
|
354
|
-
def test_job_data_as_string(self):
|
|
355
|
-
"""
|
|
356
|
-
Test that job doesn't error when not a dictionary.
|
|
357
|
-
"""
|
|
358
|
-
module = "test_object_vars"
|
|
359
|
-
name = "TestObjectVars"
|
|
360
|
-
data = "BAD DATA STRING"
|
|
361
|
-
logging.disable(logging.ERROR)
|
|
362
|
-
job_result = create_job_result_and_run_job(module, name, data=data, commit=False)
|
|
363
|
-
logging.disable(logging.NOTSET)
|
|
364
351
|
# Assert stuff
|
|
365
352
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
366
|
-
|
|
367
|
-
grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE
|
|
368
|
-
).first()
|
|
369
|
-
self.assertIn("Data should be a dictionary", log_failure.message)
|
|
353
|
+
self.assertIn("location is a required field", job_result.traceback)
|
|
370
354
|
|
|
371
355
|
def test_job_latest_result_property(self):
|
|
372
356
|
"""
|
|
373
357
|
Job test to see if the latest_result property is indeed returning the most recent job result
|
|
374
358
|
"""
|
|
375
|
-
module = "
|
|
359
|
+
module = "pass"
|
|
376
360
|
name = "TestPass"
|
|
377
|
-
job_result_1 = create_job_result_and_run_job(module, name
|
|
361
|
+
job_result_1 = create_job_result_and_run_job(module, name)
|
|
378
362
|
self.assertEqual(job_result_1.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
379
|
-
job_result_2 = create_job_result_and_run_job(module, name
|
|
363
|
+
job_result_2 = create_job_result_and_run_job(module, name)
|
|
380
364
|
self.assertEqual(job_result_2.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
381
365
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
382
366
|
self.assertGreaterEqual(job_model.job_results.count(), 2)
|
|
@@ -388,7 +372,7 @@ class JobTest(TransactionTestCase):
|
|
|
388
372
|
"""
|
|
389
373
|
Test job form with custom task queues defined on the job class
|
|
390
374
|
"""
|
|
391
|
-
module = "
|
|
375
|
+
module = "task_queues"
|
|
392
376
|
name = "TestWorkerQueues"
|
|
393
377
|
mock_get_celery_queues.return_value = {"celery": 4, "irrelevant": 5}
|
|
394
378
|
job_class, _ = get_job_class_and_model(module, name)
|
|
@@ -398,10 +382,8 @@ class JobTest(TransactionTestCase):
|
|
|
398
382
|
<td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
|
|
399
383
|
<option value="celery">celery (4 workers)</option>
|
|
400
384
|
<option value="nonexistent">nonexistent (0 workers)</option></select><br>
|
|
401
|
-
<span class="helptext">The task queue to route this job to</span
|
|
402
|
-
<
|
|
403
|
-
<td><input type="checkbox" name="_commit" placeholder="Commit changes" id="id__commit" checked><br>
|
|
404
|
-
<span class="helptext">Commit changes to the database (uncheck for a dry-run)</span></td></tr>""",
|
|
385
|
+
<span class="helptext">The task queue to route this job to</span>
|
|
386
|
+
<input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
|
|
405
387
|
form.as_table(),
|
|
406
388
|
)
|
|
407
389
|
|
|
@@ -410,7 +392,7 @@ class JobTest(TransactionTestCase):
|
|
|
410
392
|
"""
|
|
411
393
|
Test job form with custom task queues defined on the job class and overridden on the model
|
|
412
394
|
"""
|
|
413
|
-
module = "
|
|
395
|
+
module = "task_queues"
|
|
414
396
|
name = "TestWorkerQueues"
|
|
415
397
|
mock_get_celery_queues.return_value = {"default": 1, "irrelevant": 5}
|
|
416
398
|
job_class, job_model = get_job_class_and_model(module, name)
|
|
@@ -423,13 +405,64 @@ class JobTest(TransactionTestCase):
|
|
|
423
405
|
<td><select name="_task_queue" class="form-control" placeholder="Task queue" id="id__task_queue">
|
|
424
406
|
<option value="default">default (1 worker)</option>
|
|
425
407
|
<option value="priority">priority (0 workers)</option>
|
|
426
|
-
</select><br><span class="helptext">The task queue to route this job to</span
|
|
427
|
-
<
|
|
428
|
-
<td><input type="checkbox" name="_commit" placeholder="Commit changes" id="id__commit" checked><br>
|
|
429
|
-
<span class="helptext">Commit changes to the database (uncheck for a dry-run)</span></td></tr>""",
|
|
408
|
+
</select><br><span class="helptext">The task queue to route this job to</span>
|
|
409
|
+
<input type="hidden" name="_profile" value="False" id="id__profile"></td></tr>""",
|
|
430
410
|
form.as_table(),
|
|
431
411
|
)
|
|
432
412
|
|
|
413
|
+
def test_supports_dryrun(self):
|
|
414
|
+
"""
|
|
415
|
+
Test job class supports_dryrun field and job model supports_dryrun field
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
module = "dry_run"
|
|
419
|
+
name = "TestDryRun"
|
|
420
|
+
job_class, job_model = get_job_class_and_model(module, name)
|
|
421
|
+
self.assertTrue(job_class.supports_dryrun)
|
|
422
|
+
self.assertTrue(job_model.supports_dryrun)
|
|
423
|
+
|
|
424
|
+
module = "pass"
|
|
425
|
+
name = "TestPass"
|
|
426
|
+
job_class, job_model = get_job_class_and_model(module, name)
|
|
427
|
+
self.assertFalse(job_class.supports_dryrun)
|
|
428
|
+
self.assertFalse(job_model.supports_dryrun)
|
|
429
|
+
|
|
430
|
+
def test_job_profiling(self):
|
|
431
|
+
module = "profiling"
|
|
432
|
+
name = "TestProfilingJob"
|
|
433
|
+
|
|
434
|
+
# The job itself contains the 'assert' by loading the resulting profiling file from the workers filesystem
|
|
435
|
+
job_result = create_job_result_and_run_job(module, name, profile=True)
|
|
436
|
+
|
|
437
|
+
self.assertEqual(
|
|
438
|
+
job_result.status,
|
|
439
|
+
JobResultStatusChoices.STATUS_SUCCESS,
|
|
440
|
+
msg="Profiling test job errored, this indicates that either no profiling file was created or it is malformed.",
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
profiling_result = Path(f"/tmp/nautobot-jobresult-{job_result.pk}.pstats")
|
|
444
|
+
self.assertTrue(profiling_result.exists())
|
|
445
|
+
profiling_result.unlink()
|
|
446
|
+
|
|
447
|
+
def test_dryrun_default(self):
|
|
448
|
+
"""Test that dryrun_default is reflected in job form."""
|
|
449
|
+
module = "dry_run"
|
|
450
|
+
name = "TestDryRun"
|
|
451
|
+
job_class, job_model = get_job_class_and_model(module, name)
|
|
452
|
+
|
|
453
|
+
# not overridden on job model, initial form field value should match job class
|
|
454
|
+
job_model.dryrun_default_override = False
|
|
455
|
+
job_model.save()
|
|
456
|
+
form = job_class().as_form()
|
|
457
|
+
self.assertEqual(form.fields["dryrun"].initial, job_class.dryrun_default)
|
|
458
|
+
|
|
459
|
+
# overridden on job model, initial form field value should match job model
|
|
460
|
+
job_model.dryrun_default_override = True
|
|
461
|
+
job_model.dryrun_default = not job_class.dryrun_default
|
|
462
|
+
job_model.save()
|
|
463
|
+
form = job_class().as_form()
|
|
464
|
+
self.assertEqual(form.fields["dryrun"].initial, job_model.dryrun_default)
|
|
465
|
+
|
|
433
466
|
|
|
434
467
|
class JobFileUploadTest(TransactionTestCase):
|
|
435
468
|
"""Test a job that uploads/deletes files."""
|
|
@@ -449,7 +482,7 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
449
482
|
|
|
450
483
|
def test_run_job_pass(self):
|
|
451
484
|
"""Test that file upload succeeds; job SUCCEEDS; and files are deleted."""
|
|
452
|
-
module = "
|
|
485
|
+
module = "file_upload_pass"
|
|
453
486
|
name = "TestFileUploadPass"
|
|
454
487
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
455
488
|
|
|
@@ -465,9 +498,7 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
465
498
|
self.assertEqual(FileProxy.objects.count(), 1)
|
|
466
499
|
|
|
467
500
|
# Run the job
|
|
468
|
-
job_result = create_job_result_and_run_job(
|
|
469
|
-
module, name, data=serialized_data, commit=False, request=self.request
|
|
470
|
-
)
|
|
501
|
+
job_result = create_job_result_and_run_job(module, name, **serialized_data)
|
|
471
502
|
|
|
472
503
|
warning_log = JobLogEntry.objects.filter(
|
|
473
504
|
job_result=job_result, log_level=LogLevelChoices.LOG_WARNING, grouping="run"
|
|
@@ -481,7 +512,7 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
481
512
|
|
|
482
513
|
def test_run_job_fail(self):
|
|
483
514
|
"""Test that file upload succeeds; job FAILS; files deleted."""
|
|
484
|
-
module = "
|
|
515
|
+
module = "file_upload_fail"
|
|
485
516
|
name = "TestFileUploadFail"
|
|
486
517
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
487
518
|
|
|
@@ -497,13 +528,11 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
497
528
|
self.assertEqual(FileProxy.objects.count(), 1)
|
|
498
529
|
|
|
499
530
|
# Run the job
|
|
500
|
-
|
|
501
|
-
job_result = create_job_result_and_run_job(module, name, data=serialized_data, commit=False)
|
|
531
|
+
job_result = create_job_result_and_run_job(module, name, **serialized_data)
|
|
502
532
|
self.assertIsNotNone(job_result.traceback)
|
|
503
533
|
# TODO(jathan): If there are more use-cases for asserting class comparison for errors raised
|
|
504
534
|
# by Jobs, factor this into a test case method.
|
|
505
535
|
self.assertIn(job_class.exception.__name__, job_result.traceback)
|
|
506
|
-
logging.disable(logging.NOTSET)
|
|
507
536
|
|
|
508
537
|
# Assert that file contents were correctly read
|
|
509
538
|
self.assertEqual(
|
|
@@ -512,13 +541,6 @@ class JobFileUploadTest(TransactionTestCase):
|
|
|
512
541
|
.message,
|
|
513
542
|
f"File contents: {self.file_contents}",
|
|
514
543
|
)
|
|
515
|
-
# Also ensure the standard log message about aborting the transaction is present
|
|
516
|
-
self.assertEqual(
|
|
517
|
-
JobLogEntry.objects.filter(job_result=job_result, log_level=LogLevelChoices.LOG_INFO, grouping="run")
|
|
518
|
-
.first()
|
|
519
|
-
.message,
|
|
520
|
-
"Database changes have been reverted due to error.",
|
|
521
|
-
)
|
|
522
544
|
|
|
523
545
|
# Assert that FileProxy was cleaned up
|
|
524
546
|
self.assertEqual(FileProxy.objects.count(), 0)
|
|
@@ -541,80 +563,47 @@ class RunJobManagementCommandTest(TransactionTestCase):
|
|
|
541
563
|
|
|
542
564
|
def test_runjob_nochange_successful(self):
|
|
543
565
|
"""Basic success-path test for Jobs that don't modify the Nautobot database."""
|
|
544
|
-
module = "
|
|
566
|
+
module = "pass"
|
|
545
567
|
name = "TestPass"
|
|
546
568
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
547
569
|
|
|
548
|
-
out, err = self.run_command("--no-color", job_model.class_path)
|
|
549
|
-
self.assertIn(f"Running {job_model.class_path}...", out)
|
|
550
|
-
self.assertIn(f"{module}: 1 success, 1 info, 0 warning, 0 failure", out)
|
|
551
|
-
self.assertIn("success: None", out)
|
|
552
|
-
self.assertIn("info: Database changes have been reverted automatically.", out)
|
|
553
|
-
self.assertIn(f"{job_model.class_path}: SUCCESS", out)
|
|
554
|
-
self.assertEqual("", err)
|
|
555
|
-
|
|
556
|
-
def test_runjob_db_change_no_commit(self):
|
|
557
|
-
"""A job that changes the DB, when run with commit=False, doesn't modify the database."""
|
|
558
|
-
with self.assertRaises(ObjectDoesNotExist):
|
|
559
|
-
Status.objects.get(slug="test-status")
|
|
560
|
-
|
|
561
|
-
module = "test_modify_db"
|
|
562
|
-
name = "TestModifyDB"
|
|
563
|
-
_job_class, job_model = get_job_class_and_model(module, name)
|
|
564
|
-
|
|
565
|
-
out, err = self.run_command("--no-color", job_model.class_path)
|
|
570
|
+
out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
|
|
566
571
|
self.assertIn(f"Running {job_model.class_path}...", out)
|
|
567
|
-
self.assertIn(
|
|
568
|
-
self.assertIn("
|
|
569
|
-
self.assertIn("info: Database changes have been reverted automatically.", out)
|
|
572
|
+
self.assertIn("run: 0 debug, 1 info, 0 warning, 0 error, 0 critical", out)
|
|
573
|
+
self.assertIn("info: Success", out)
|
|
570
574
|
self.assertIn(f"{job_model.class_path}: SUCCESS", out)
|
|
571
575
|
self.assertEqual("", err)
|
|
572
576
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
info_log = JobLogEntry.objects.filter(log_level=LogLevelChoices.LOG_INFO).first()
|
|
577
|
-
self.assertEqual("Database changes have been reverted automatically.", info_log.message)
|
|
578
|
-
|
|
579
|
-
def test_runjob_db_change_commit_no_username(self):
|
|
580
|
-
"""A job that changes the DB, when run with commit=True but no username, is rejected."""
|
|
581
|
-
module = "test_modify_db"
|
|
577
|
+
def test_runjob_wrong_username(self):
|
|
578
|
+
"""A job when run with a nonexistent username, is rejected."""
|
|
579
|
+
module = "modify_db"
|
|
582
580
|
name = "TestModifyDB"
|
|
583
581
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
584
582
|
with self.assertRaises(CommandError):
|
|
585
|
-
self.run_command("--
|
|
583
|
+
self.run_command("--username", "nosuchuser", job_model.class_path)
|
|
586
584
|
|
|
587
|
-
def
|
|
588
|
-
"""A job that changes the DB,
|
|
589
|
-
module = "
|
|
590
|
-
name = "TestModifyDB"
|
|
591
|
-
_job_class, job_model = get_job_class_and_model(module, name)
|
|
592
|
-
with self.assertRaises(CommandError):
|
|
593
|
-
self.run_command("--commit", "--username", "nosuchuser", job_model.class_path)
|
|
594
|
-
|
|
595
|
-
def test_runjob_db_change_commit_and_username(self):
|
|
596
|
-
"""A job that changes the DB, when run with commit=True and a username, successfully updates the DB."""
|
|
597
|
-
get_user_model().objects.create(username="test_user")
|
|
598
|
-
|
|
599
|
-
module = "test_modify_db"
|
|
585
|
+
def test_runjob_db_change(self):
|
|
586
|
+
"""A job that changes the DB, successfully updates the DB."""
|
|
587
|
+
module = "modify_db"
|
|
600
588
|
name = "TestModifyDB"
|
|
601
589
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
602
590
|
|
|
603
|
-
out, err = self.run_command("--
|
|
591
|
+
out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
|
|
604
592
|
self.assertIn(f"Running {job_model.class_path}...", out)
|
|
605
593
|
# Changed job to actually log data. Can't display empty results if no logs were created.
|
|
606
|
-
self.assertIn(
|
|
594
|
+
self.assertIn("run: 0 debug, 1 info, 0 warning, 0 error, 0 critical", out)
|
|
607
595
|
self.assertIn(f"{job_model.class_path}: SUCCESS", out)
|
|
608
596
|
self.assertEqual("", err)
|
|
609
597
|
|
|
610
|
-
success_log = JobLogEntry.objects.filter(
|
|
611
|
-
|
|
598
|
+
success_log = JobLogEntry.objects.filter(
|
|
599
|
+
log_level=LogLevelChoices.LOG_INFO, message="Status created successfully."
|
|
600
|
+
)
|
|
601
|
+
self.assertTrue(success_log.exists())
|
|
602
|
+
self.assertEqual(success_log.count(), 1)
|
|
612
603
|
|
|
613
|
-
status = Status.objects.get(
|
|
604
|
+
status = Status.objects.get(name="Test Status")
|
|
614
605
|
self.assertEqual(status.name, "Test Status")
|
|
615
606
|
|
|
616
|
-
status.delete()
|
|
617
|
-
|
|
618
607
|
|
|
619
608
|
class JobLocationCustomFieldTest(TransactionTestCase):
|
|
620
609
|
"""Test a job that creates a location and a custom field."""
|
|
@@ -629,9 +618,9 @@ class JobLocationCustomFieldTest(TransactionTestCase):
|
|
|
629
618
|
self.request.user = self.user
|
|
630
619
|
|
|
631
620
|
def test_run(self):
|
|
632
|
-
module = "
|
|
621
|
+
module = "location_with_custom_field"
|
|
633
622
|
name = "TestCreateLocationWithCustomField"
|
|
634
|
-
job_result = create_job_result_and_run_job(module, name
|
|
623
|
+
job_result = create_job_result_and_run_job(module, name)
|
|
635
624
|
job_result.refresh_from_db()
|
|
636
625
|
|
|
637
626
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
@@ -639,13 +628,17 @@ class JobLocationCustomFieldTest(TransactionTestCase):
|
|
|
639
628
|
# Test location with a value for custom_field
|
|
640
629
|
location_1 = Location.objects.filter(slug="test-location-one")
|
|
641
630
|
self.assertEqual(location_1.count(), 1)
|
|
631
|
+
location_1 = location_1.first()
|
|
642
632
|
self.assertEqual(CustomField.objects.filter(label="cf1").count(), 1)
|
|
643
|
-
self.
|
|
633
|
+
self.assertIn("cf1", location_1.cf)
|
|
634
|
+
self.assertEqual(location_1.cf["cf1"], "some-value")
|
|
644
635
|
|
|
645
636
|
# Test location with default value for custom field
|
|
646
637
|
location_2 = Location.objects.filter(slug="test-location-two")
|
|
647
638
|
self.assertEqual(location_2.count(), 1)
|
|
648
|
-
|
|
639
|
+
location_2 = location_2.first()
|
|
640
|
+
self.assertIn("cf1", location_2.cf)
|
|
641
|
+
self.assertEqual(location_2.cf["cf1"], "-")
|
|
649
642
|
|
|
650
643
|
|
|
651
644
|
class JobButtonReceiverTest(TransactionTestCase):
|
|
@@ -662,7 +655,10 @@ class JobButtonReceiverTest(TransactionTestCase):
|
|
|
662
655
|
self.request.user = self.user
|
|
663
656
|
|
|
664
657
|
self.location_type = LocationType.objects.create(name="Test Root Type 2")
|
|
665
|
-
|
|
658
|
+
status = Status.objects.get_for_model(Location).first()
|
|
659
|
+
self.location = Location.objects.create(
|
|
660
|
+
name="Test Job Button Location 1", location_type=self.location_type, status=status
|
|
661
|
+
)
|
|
666
662
|
content_type = ContentType.objects.get_for_model(Location)
|
|
667
663
|
self.data = {
|
|
668
664
|
"object_pk": self.location.pk,
|
|
@@ -670,38 +666,37 @@ class JobButtonReceiverTest(TransactionTestCase):
|
|
|
670
666
|
}
|
|
671
667
|
|
|
672
668
|
def test_form_field(self):
|
|
673
|
-
module = "
|
|
669
|
+
module = "job_button_receiver"
|
|
674
670
|
name = "TestJobButtonReceiverSimple"
|
|
675
671
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
676
672
|
form = job_class().as_form()
|
|
677
|
-
self.assertSequenceEqual(
|
|
673
|
+
self.assertSequenceEqual(
|
|
674
|
+
list(form.fields.keys()), ["object_pk", "object_model_name", "_task_queue", "_profile"]
|
|
675
|
+
)
|
|
678
676
|
|
|
679
677
|
def test_hidden(self):
|
|
680
|
-
module = "
|
|
678
|
+
module = "job_button_receiver"
|
|
681
679
|
name = "TestJobButtonReceiverSimple"
|
|
682
680
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
683
681
|
self.assertFalse(job_model.hidden)
|
|
684
682
|
|
|
685
683
|
def test_is_job_button(self):
|
|
686
|
-
|
|
687
684
|
with self.subTest(expected=False):
|
|
688
|
-
module = "
|
|
685
|
+
module = "pass"
|
|
689
686
|
name = "TestPass"
|
|
690
687
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
691
688
|
self.assertFalse(job_model.is_job_button_receiver)
|
|
692
689
|
|
|
693
690
|
with self.subTest(expected=True):
|
|
694
|
-
module = "
|
|
691
|
+
module = "job_button_receiver"
|
|
695
692
|
name = "TestJobButtonReceiverSimple"
|
|
696
693
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
697
694
|
self.assertTrue(job_model.is_job_button_receiver)
|
|
698
695
|
|
|
699
696
|
def test_missing_receive_job_button_method(self):
|
|
700
|
-
module = "
|
|
697
|
+
module = "job_button_receiver"
|
|
701
698
|
name = "TestJobButtonReceiverFail"
|
|
702
|
-
|
|
703
|
-
job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
|
|
704
|
-
logging.disable(logging.NOTSET)
|
|
699
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
705
700
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
706
701
|
|
|
707
702
|
|
|
@@ -721,53 +716,52 @@ class JobHookReceiverTest(TransactionTestCase):
|
|
|
721
716
|
# generate an ObjectChange by creating a new location
|
|
722
717
|
with web_request_context(self.user):
|
|
723
718
|
location_type = LocationType.objects.create(name="Test Root Type 1")
|
|
724
|
-
|
|
719
|
+
status = Status.objects.get_for_model(Location).first()
|
|
720
|
+
location = Location(name="Test Location 1", location_type=location_type, status=status)
|
|
725
721
|
location.save()
|
|
726
722
|
location.refresh_from_db()
|
|
727
723
|
oc = get_changes_for_model(location).first()
|
|
728
724
|
self.data = {"object_change": oc.id}
|
|
729
725
|
|
|
730
726
|
def test_form_field(self):
|
|
731
|
-
module = "
|
|
727
|
+
module = "job_hook_receiver"
|
|
732
728
|
name = "TestJobHookReceiverLog"
|
|
733
729
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
734
730
|
form = job_class().as_form()
|
|
735
|
-
self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "
|
|
731
|
+
self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_profile"])
|
|
736
732
|
|
|
737
733
|
def test_hidden(self):
|
|
738
|
-
module = "
|
|
734
|
+
module = "job_hook_receiver"
|
|
739
735
|
name = "TestJobHookReceiverLog"
|
|
740
736
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
741
737
|
self.assertFalse(job_model.hidden)
|
|
742
738
|
|
|
743
739
|
def test_is_job_hook(self):
|
|
744
740
|
with self.subTest(expected=False):
|
|
745
|
-
module = "
|
|
741
|
+
module = "pass"
|
|
746
742
|
name = "TestPass"
|
|
747
743
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
748
744
|
self.assertFalse(job_model.is_job_hook_receiver)
|
|
749
745
|
|
|
750
746
|
with self.subTest(expected=True):
|
|
751
|
-
module = "
|
|
747
|
+
module = "job_hook_receiver"
|
|
752
748
|
name = "TestJobHookReceiverLog"
|
|
753
749
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
754
750
|
self.assertTrue(job_model.is_job_hook_receiver)
|
|
755
751
|
|
|
756
752
|
def test_object_change_context(self):
|
|
757
|
-
module = "
|
|
753
|
+
module = "job_hook_receiver"
|
|
758
754
|
name = "TestJobHookReceiverChange"
|
|
759
|
-
create_job_result_and_run_job(module, name,
|
|
755
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
760
756
|
test_location = Location.objects.get(name="test_jhr")
|
|
761
757
|
oc = get_changes_for_model(test_location).first()
|
|
762
758
|
self.assertEqual(oc.change_context, ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK)
|
|
763
|
-
self.assertEqual(oc.user_id,
|
|
759
|
+
self.assertEqual(oc.user_id, job_result.user.pk)
|
|
764
760
|
|
|
765
761
|
def test_missing_receive_job_hook_method(self):
|
|
766
|
-
module = "
|
|
762
|
+
module = "job_hook_receiver"
|
|
767
763
|
name = "TestJobHookReceiverFail"
|
|
768
|
-
|
|
769
|
-
job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
|
|
770
|
-
logging.disable(logging.NOTSET)
|
|
764
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
771
765
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
772
766
|
|
|
773
767
|
|
|
@@ -779,7 +773,7 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
779
773
|
def setUp(self):
|
|
780
774
|
super().setUp()
|
|
781
775
|
|
|
782
|
-
module = "
|
|
776
|
+
module = "job_hook_receiver"
|
|
783
777
|
name = "TestJobHookReceiverLog"
|
|
784
778
|
self.job_class, self.job_model = get_job_class_and_model(module, name)
|
|
785
779
|
job_hook = JobHook(
|
|
@@ -794,13 +788,16 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
794
788
|
|
|
795
789
|
def test_enqueue_job_hook(self):
|
|
796
790
|
with web_request_context(user=self.user):
|
|
797
|
-
|
|
791
|
+
status = Status.objects.get_for_model(Location).first()
|
|
792
|
+
Location.objects.create(name="Test Job Hook Location 1", location_type=self.location_type, status=status)
|
|
798
793
|
job_result = JobResult.objects.get(job_model=self.job_model)
|
|
799
794
|
expected_log_messages = [
|
|
795
|
+
("info", "Running job"),
|
|
800
796
|
("info", f"change: dcim | location Test Job Hook Location 1 created by {self.user.username}"),
|
|
801
797
|
("info", "action: create"),
|
|
802
|
-
("info", f"
|
|
803
|
-
("
|
|
798
|
+
("info", f"jobresult.user: {self.user.username}"),
|
|
799
|
+
("info", "Test Job Hook Location 1"),
|
|
800
|
+
("info", "Job completed"),
|
|
804
801
|
]
|
|
805
802
|
log_messages = JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
|
|
806
803
|
self.assertSequenceEqual(log_messages, expected_log_messages)
|
|
@@ -808,8 +805,9 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
808
805
|
@mock.patch.object(JobResult, "enqueue_job")
|
|
809
806
|
def test_enqueue_job_hook_skipped(self, mock_enqueue_job):
|
|
810
807
|
change_context = JobHookChangeContext(user=self.user)
|
|
808
|
+
status = Status.objects.get_for_model(Location).first()
|
|
811
809
|
with change_logging(change_context):
|
|
812
|
-
Location.objects.create(name="Test Job Hook Location 2", location_type=self.location_type)
|
|
810
|
+
Location.objects.create(name="Test Job Hook Location 2", location_type=self.location_type, status=status)
|
|
813
811
|
|
|
814
812
|
self.assertFalse(mock_enqueue_job.called)
|
|
815
813
|
|
|
@@ -819,8 +817,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
819
817
|
for i in range(1, 7):
|
|
820
818
|
ScheduledJob.objects.create(
|
|
821
819
|
name=f"test{i}",
|
|
822
|
-
task="
|
|
823
|
-
job_class="
|
|
820
|
+
task="pass.TestPass",
|
|
821
|
+
job_class="pass.TestPass",
|
|
824
822
|
interval=JobExecutionType.TYPE_FUTURE,
|
|
825
823
|
user=self.user,
|
|
826
824
|
start_time=timezone.now() - datetime.timedelta(days=i * 30),
|
|
@@ -829,8 +827,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
829
827
|
|
|
830
828
|
ScheduledJob.objects.create(
|
|
831
829
|
name="test7",
|
|
832
|
-
task="
|
|
833
|
-
job_class="
|
|
830
|
+
task="pass.TestPass",
|
|
831
|
+
job_class="pass.TestPass",
|
|
834
832
|
interval=JobExecutionType.TYPE_DAILY,
|
|
835
833
|
user=self.user,
|
|
836
834
|
start_time=timezone.now() - datetime.timedelta(days=180),
|
|
@@ -858,8 +856,8 @@ class ScheduledJobIntervalTestCase(TestCase):
|
|
|
858
856
|
start_time = timezone.now() + datetime.timedelta(days=6)
|
|
859
857
|
scheduled_job = ScheduledJob.objects.create(
|
|
860
858
|
name="weekly_interval",
|
|
861
|
-
task="
|
|
862
|
-
job_class="
|
|
859
|
+
task="pass.TestPass",
|
|
860
|
+
job_class="pass.TestPass",
|
|
863
861
|
interval=JobExecutionType.TYPE_WEEKLY,
|
|
864
862
|
user=self.user,
|
|
865
863
|
start_time=start_time,
|