nautobot 2.0.0a3__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/apps/api.py +6 -8
- nautobot/apps/forms.py +0 -2
- nautobot/apps/ui.py +0 -8
- nautobot/circuits/api/serializers.py +9 -117
- nautobot/circuits/api/urls.py +1 -1
- nautobot/circuits/api/views.py +0 -1
- nautobot/circuits/forms.py +0 -65
- nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
- 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 -87
- nautobot/circuits/navigation.py +14 -69
- nautobot/circuits/signals.py +0 -2
- nautobot/circuits/tables.py +39 -1
- nautobot/circuits/tests/integration/test_relationships.py +9 -9
- nautobot/circuits/tests/test_api.py +4 -8
- nautobot/circuits/tests/test_filters.py +10 -4
- nautobot/circuits/tests/test_models.py +5 -1
- nautobot/circuits/tests/test_views.py +27 -5
- nautobot/circuits/views.py +18 -10
- nautobot/core/api/__init__.py +8 -2
- 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 +4 -0
- nautobot/core/api/parsers.py +154 -0
- nautobot/core/api/renderers.py +153 -2
- nautobot/core/api/schema.py +46 -2
- nautobot/core/api/serializers.py +377 -35
- 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 -72
- nautobot/core/apps/__init__.py +138 -220
- nautobot/core/celery/__init__.py +112 -41
- nautobot/core/celery/backends.py +19 -12
- 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 +0 -113
- nautobot/core/{cli.py → cli/__init__.py} +1 -1
- nautobot/core/cli/__main__.py +3 -0
- nautobot/core/constants.py +0 -24
- 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 +38 -65
- nautobot/core/forms/forms.py +4 -1
- nautobot/core/forms/utils.py +0 -52
- 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/generate_test_data.py +3 -2
- nautobot/core/management/commands/post_upgrade.py +24 -24
- nautobot/core/models/__init__.py +26 -1
- nautobot/core/models/fields.py +24 -5
- nautobot/core/models/generics.py +2 -42
- nautobot/core/models/managers.py +5 -0
- nautobot/core/models/name_color_content_types.py +0 -14
- nautobot/core/models/tree_queries.py +14 -4
- nautobot/core/models/utils.py +5 -6
- nautobot/core/models/validators.py +17 -8
- nautobot/core/releases.py +8 -10
- nautobot/core/settings.py +80 -42
- 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 +3 -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 -20
- 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 -28
- nautobot/core/testing/__init__.py +47 -44
- nautobot/core/testing/api.py +362 -47
- nautobot/core/testing/filters.py +1 -1
- nautobot/core/testing/migrations.py +2 -0
- nautobot/core/testing/mixins.py +22 -9
- nautobot/core/testing/schema.py +2 -1
- nautobot/core/testing/views.py +21 -46
- 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/test_api.py +290 -21
- nautobot/core/tests/test_checks.py +0 -7
- nautobot/core/tests/test_filters.py +107 -59
- nautobot/core/tests/test_forms.py +26 -92
- nautobot/core/tests/test_graphql.py +110 -77
- nautobot/core/tests/test_logging.py +4 -0
- nautobot/core/tests/test_managers.py +3 -1
- nautobot/core/tests/test_models.py +2 -0
- nautobot/core/tests/test_paginator.py +3 -1
- nautobot/core/tests/test_releases.py +12 -12
- nautobot/core/tests/test_templatetags_helpers.py +4 -4
- nautobot/core/tests/test_utils.py +32 -68
- nautobot/core/tests/test_views.py +12 -15
- nautobot/core/utils/data.py +17 -0
- nautobot/core/utils/deprecation.py +9 -6
- nautobot/core/utils/filtering.py +8 -3
- nautobot/core/utils/git.py +12 -4
- nautobot/core/utils/lookup.py +3 -1
- nautobot/core/utils/requests.py +1 -104
- nautobot/core/views/__init__.py +1 -0
- nautobot/core/views/generic.py +75 -110
- nautobot/core/views/mixins.py +52 -61
- nautobot/core/views/renderers.py +6 -7
- nautobot/core/views/utils.py +80 -0
- nautobot/dcim/api/serializers.py +160 -667
- nautobot/dcim/api/urls.py +1 -1
- nautobot/dcim/api/views.py +7 -44
- nautobot/dcim/choices.py +2 -0
- nautobot/dcim/filters/__init__.py +21 -0
- nautobot/dcim/form_mixins.py +1 -27
- nautobot/dcim/forms.py +19 -765
- nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
- nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
- nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
- nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
- nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
- nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
- nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
- nautobot/dcim/migrations/0040_tagsfield.py +109 -0
- nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
- nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
- nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
- nautobot/dcim/models/cables.py +3 -33
- nautobot/dcim/models/device_component_templates.py +6 -0
- nautobot/dcim/models/device_components.py +12 -198
- nautobot/dcim/models/devices.py +30 -143
- nautobot/dcim/models/locations.py +3 -64
- nautobot/dcim/models/power.py +3 -50
- nautobot/dcim/models/racks.py +7 -84
- nautobot/dcim/navigation.py +141 -467
- nautobot/dcim/signals.py +0 -2
- nautobot/dcim/tables/locations.py +2 -2
- nautobot/dcim/tables/power.py +1 -2
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/devicetype.html +2 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -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/test_api.py +166 -81
- nautobot/dcim/tests/test_cablepaths.py +41 -35
- nautobot/dcim/tests/test_filters.py +67 -23
- nautobot/dcim/tests/test_forms.py +5 -205
- nautobot/dcim/tests/test_graphql.py +7 -2
- nautobot/dcim/tests/test_migrations.py +6 -11
- nautobot/dcim/tests/test_models.py +182 -110
- nautobot/dcim/tests/test_natural_ordering.py +11 -8
- nautobot/dcim/tests/test_signals.py +6 -3
- nautobot/dcim/tests/test_views.py +197 -175
- nautobot/dcim/urls.py +11 -16
- nautobot/dcim/views.py +7 -134
- 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 +177 -195
- nautobot/docs/administration/nautobot-server.md +6 -21
- nautobot/docs/administration/replicating-nautobot.md +0 -10
- nautobot/docs/configuration/optional-settings.md +32 -41
- nautobot/docs/configuration/required-settings.md +11 -52
- nautobot/docs/development/application-registry.md +2 -13
- nautobot/docs/development/extending-models.md +15 -17
- nautobot/docs/development/generic-views.md +0 -2
- nautobot/docs/development/getting-started.md +55 -5
- nautobot/docs/development/navigation-menu.md +22 -93
- nautobot/docs/development/react-ui.md +105 -0
- nautobot/docs/development/role-internals.md +1 -3
- nautobot/docs/development/style-guide.md +6 -4
- nautobot/docs/index.md +3 -2
- nautobot/docs/installation/migrating-from-netbox.md +11 -42
- 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 +170 -747
- 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/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/virtualization/virtualmachine.md +3 -0
- nautobot/docs/plugins/development.md +83 -21
- nautobot/docs/release-notes/version-1.5.md +53 -0
- nautobot/docs/release-notes/version-2.0.md +180 -0
- nautobot/docs/requirements.txt +1 -0
- 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 -158
- nautobot/extras/api/serializers.py +165 -700
- nautobot/extras/api/urls.py +1 -1
- nautobot/extras/api/views.py +294 -280
- nautobot/extras/apps.py +4 -7
- nautobot/extras/choices.py +11 -9
- 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/filters/__init__.py +20 -19
- nautobot/extras/filters/mixins.py +4 -4
- nautobot/extras/forms/forms.py +63 -127
- nautobot/extras/forms/mixins.py +23 -51
- nautobot/extras/health_checks.py +0 -33
- nautobot/extras/jobs.py +387 -565
- nautobot/extras/management/commands/runjob.py +24 -62
- nautobot/extras/managers.py +30 -7
- 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 -1
- nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
- nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
- nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
- nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
- nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
- nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
- nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
- nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
- nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
- nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
- 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 -1
- 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 -9
- nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
- 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/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
- 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 -6
- nautobot/extras/models/jobs.py +189 -321
- nautobot/extras/models/mixins.py +0 -71
- nautobot/extras/models/models.py +0 -19
- nautobot/extras/models/relationships.py +19 -13
- nautobot/extras/models/roles.py +0 -34
- nautobot/extras/models/secrets.py +2 -26
- 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 -120
- nautobot/extras/plugins/utils.py +0 -3
- nautobot/extras/plugins/validators.py +5 -4
- nautobot/extras/plugins/views.py +16 -3
- nautobot/extras/querysets.py +1 -7
- nautobot/extras/registry.py +3 -0
- nautobot/extras/signals.py +26 -60
- nautobot/extras/tables.py +34 -40
- 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/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 +15 -17
- nautobot/extras/templates/extras/jobresult.html +24 -6
- nautobot/extras/templates/extras/scheduledjob.html +2 -2
- nautobot/extras/templates/extras/secret.html +28 -0
- nautobot/extras/templatetags/job_buttons.py +1 -0
- 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/test_computedfields.py +1 -1
- nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
- nautobot/extras/tests/integration/test_customfields.py +4 -2
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_jobs.py +25 -27
- nautobot/extras/tests/integration/test_notes.py +8 -4
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_api.py +649 -642
- nautobot/extras/tests/test_changelog.py +3 -3
- nautobot/extras/tests/test_context_managers.py +5 -3
- nautobot/extras/tests/test_customfields.py +92 -50
- nautobot/extras/tests/test_datasources.py +189 -112
- nautobot/extras/tests/test_dynamicgroups.py +7 -8
- nautobot/extras/tests/test_filters.py +137 -89
- nautobot/extras/tests/test_forms.py +73 -75
- nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
- nautobot/extras/tests/test_jobs.py +262 -263
- nautobot/extras/tests/test_migrations.py +4 -3
- nautobot/extras/tests/test_models.py +116 -161
- nautobot/extras/tests/test_plugins.py +38 -60
- nautobot/extras/tests/test_relationships.py +167 -120
- nautobot/extras/tests/test_tags.py +6 -11
- nautobot/extras/tests/test_utils.py +31 -1
- nautobot/extras/tests/test_views.py +201 -145
- nautobot/extras/tests/test_webhooks.py +6 -2
- nautobot/extras/urls.py +42 -42
- nautobot/extras/utils.py +137 -163
- nautobot/extras/views.py +78 -152
- nautobot/ipam/api/fields.py +17 -0
- nautobot/ipam/api/serializers.py +58 -164
- nautobot/ipam/api/urls.py +1 -1
- nautobot/ipam/api/views.py +3 -2
- nautobot/ipam/apps.py +1 -2
- nautobot/ipam/filters.py +1 -10
- nautobot/ipam/forms.py +4 -177
- nautobot/ipam/lookups.py +1 -0
- nautobot/ipam/management/commands/__init__.py +0 -0
- nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
- nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
- nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
- nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
- nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
- nautobot/ipam/migrations/0028_tagsfield.py +44 -0
- nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
- nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
- 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 +100 -236
- nautobot/ipam/navigation.py +36 -181
- nautobot/ipam/querysets.py +20 -25
- nautobot/ipam/signals.py +49 -6
- nautobot/ipam/tables.py +10 -3
- 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 +17 -4
- nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +0 -13
- nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
- nautobot/ipam/tests/integration/test_prefixes.py +3 -26
- nautobot/ipam/tests/test_api.py +22 -19
- nautobot/ipam/tests/test_filters.py +59 -23
- nautobot/ipam/tests/test_migrations.py +6 -10
- nautobot/ipam/tests/test_models.py +323 -198
- nautobot/ipam/tests/test_ordering.py +2 -2
- nautobot/ipam/tests/test_querysets.py +44 -24
- nautobot/ipam/tests/test_views.py +73 -26
- nautobot/ipam/urls.py +16 -0
- nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
- nautobot/ipam/utils/migrations.py +713 -0
- nautobot/ipam/views.py +137 -20
- nautobot/project-static/docs/404.html +1178 -10
- nautobot/project-static/docs/additional-features/caching.html +1224 -159
- nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
- nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
- nautobot/project-static/docs/additional-features/graphql.html +1179 -11
- nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
- nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
- nautobot/project-static/docs/additional-features/jobs.html +1514 -328
- nautobot/project-static/docs/additional-features/napalm.html +1180 -12
- nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
- nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
- nautobot/project-static/docs/administration/celery-queues.html +1178 -10
- nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
- nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
- nautobot/project-static/docs/administration/permissions.html +1178 -10
- nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
- nautobot/project-static/docs/apps/index.html +1178 -10
- nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
- nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
- nautobot/project-static/docs/configuration/index.html +1178 -10
- nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
- nautobot/project-static/docs/configuration/required-settings.html +1312 -211
- nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
- nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
- nautobot/project-static/docs/core-functionality/devices.html +1182 -10
- nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
- nautobot/project-static/docs/core-functionality/power.html +1178 -10
- nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
- nautobot/project-static/docs/core-functionality/services.html +1178 -10
- nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
- nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
- nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
- nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
- nautobot/project-static/docs/development/application-registry.html +1190 -42
- nautobot/project-static/docs/development/best-practices.html +1178 -10
- nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
- nautobot/project-static/docs/development/extending-models.html +1238 -83
- nautobot/project-static/docs/development/generic-views.html +1180 -14
- nautobot/project-static/docs/development/getting-started.html +1365 -90
- nautobot/project-static/docs/development/homepage.html +1178 -10
- nautobot/project-static/docs/development/index.html +1178 -10
- nautobot/project-static/docs/development/model-features.html +1178 -10
- nautobot/project-static/docs/development/natural-keys.html +1178 -10
- nautobot/project-static/docs/development/navigation-menu.html +1215 -125
- nautobot/project-static/docs/development/react-ui.html +4199 -0
- nautobot/project-static/docs/development/release-checklist.html +1178 -10
- nautobot/project-static/docs/development/role-internals.html +1179 -12
- nautobot/project-static/docs/development/style-guide.html +1188 -19
- nautobot/project-static/docs/development/templates.html +1178 -10
- nautobot/project-static/docs/development/testing.html +1178 -10
- nautobot/project-static/docs/development/user-preferences.html +1178 -10
- nautobot/project-static/docs/docker/index.html +1178 -10
- nautobot/project-static/docs/index.html +1183 -12
- nautobot/project-static/docs/installation/centos.html +1178 -10
- nautobot/project-static/docs/installation/external-authentication.html +1178 -10
- nautobot/project-static/docs/installation/http-server.html +1178 -10
- nautobot/project-static/docs/installation/index.html +1178 -10
- nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
- nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
- nautobot/project-static/docs/installation/nautobot.html +1179 -11
- nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
- nautobot/project-static/docs/installation/services.html +1178 -10
- 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 +1178 -10
- nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
- nautobot/project-static/docs/installation/upgrading.html +1178 -10
- nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
- nautobot/project-static/docs/models/circuits/provider.html +1293 -103
- nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
- nautobot/project-static/docs/models/dcim/cable.html +1324 -103
- nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/device.html +1326 -132
- nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
- nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
- nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/interface.html +1293 -103
- nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
- nautobot/project-static/docs/models/dcim/location.html +1293 -103
- nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
- nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
- nautobot/project-static/docs/models/dcim/platform.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
- nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
- nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
- nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/rack.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
- nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
- nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
- nautobot/project-static/docs/models/dcim/region.html +1178 -10
- nautobot/project-static/docs/models/dcim/site.html +1178 -10
- nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
- nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
- nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
- nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
- nautobot/project-static/docs/models/extras/customfield.html +1180 -12
- nautobot/project-static/docs/models/extras/customlink.html +1180 -12
- nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
- nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
- nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
- nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
- nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
- nautobot/project-static/docs/models/extras/job.html +1277 -86
- nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
- nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
- nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
- nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
- nautobot/project-static/docs/models/extras/note.html +1180 -12
- nautobot/project-static/docs/models/extras/relationship.html +1182 -14
- nautobot/project-static/docs/models/extras/role.html +1320 -86
- nautobot/project-static/docs/models/extras/secret.html +1314 -86
- nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
- nautobot/project-static/docs/models/extras/status.html +1188 -59
- nautobot/project-static/docs/models/extras/tag.html +1180 -12
- nautobot/project-static/docs/models/extras/webhook.html +1180 -12
- nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
- nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
- nautobot/project-static/docs/models/ipam/rir.html +1276 -86
- nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
- nautobot/project-static/docs/models/ipam/service.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
- nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
- nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
- nautobot/project-static/docs/models/users/token.html +1276 -86
- nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
- nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
- nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/plugins/development.html +1726 -495
- nautobot/project-static/docs/plugins/index.html +1178 -10
- nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
- nautobot/project-static/docs/release-notes/index.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
- nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
- nautobot/project-static/docs/requirements.txt +1 -0
- nautobot/project-static/docs/rest-api/authentication.html +1179 -11
- nautobot/project-static/docs/rest-api/filtering.html +1178 -10
- nautobot/project-static/docs/rest-api/overview.html +1841 -446
- 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 +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
- nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
- nautobot/project-static/docs/user-guides/graphql.html +1178 -10
- nautobot/project-static/docs/user-guides/relationships.html +1178 -10
- nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
- nautobot/project-static/js/forms.js +16 -9
- nautobot/project-static/js/theme.js +5 -0
- nautobot/tenancy/api/serializers.py +4 -32
- nautobot/tenancy/api/urls.py +1 -1
- nautobot/tenancy/forms.py +0 -28
- nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
- nautobot/tenancy/models.py +0 -25
- nautobot/tenancy/navigation.py +6 -39
- nautobot/tenancy/templates/tenancy/tenant.html +12 -12
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/tests/test_api.py +1 -3
- nautobot/tenancy/tests/test_filters.py +10 -5
- nautobot/tenancy/views.py +0 -2
- 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/tests/test_api.py +110 -25
- nautobot/virtualization/api/serializers.py +18 -130
- nautobot/virtualization/api/urls.py +1 -1
- nautobot/virtualization/api/views.py +1 -22
- nautobot/virtualization/forms.py +13 -99
- nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
- nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
- nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
- nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
- 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/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
- nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
- nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
- nautobot/virtualization/models.py +31 -123
- nautobot/virtualization/navigation.py +18 -99
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
- nautobot/virtualization/tests/test_api.py +25 -26
- nautobot/virtualization/tests/test_filters.py +41 -15
- nautobot/virtualization/tests/test_models.py +31 -7
- nautobot/virtualization/tests/test_views.py +42 -25
- nautobot/virtualization/views.py +7 -6
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
- {nautobot-2.0.0a3.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 -41
- nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
- 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 -18
- 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 -159
- nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
- nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
- nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
- nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
- 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.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.0a3.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,7 +232,7 @@ 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
|
|
|
@@ -255,7 +251,7 @@ class JobTest(TransactionTestCase):
|
|
|
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)
|
|
349
|
+
job_result = create_job_result_and_run_job(module, name, **data)
|
|
346
350
|
|
|
347
351
|
# Assert stuff
|
|
348
352
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
349
|
-
|
|
350
|
-
grouping="initialization", log_level=LogLevelChoices.LOG_FAILURE
|
|
351
|
-
).first()
|
|
352
|
-
self.assertIn("location is a required field", log_failure.message)
|
|
353
|
-
|
|
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
|
-
# Assert stuff
|
|
365
|
-
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
366
|
-
log_failure = JobLogEntry.objects.filter(
|
|
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)
|
|
570
|
+
out, err = self.run_command("--local", "--no-color", "--username", self.user.username, job_model.class_path)
|
|
549
571
|
self.assertIn(f"Running {job_model.class_path}...", out)
|
|
550
|
-
self.assertIn(
|
|
551
|
-
self.assertIn("
|
|
552
|
-
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)
|
|
553
574
|
self.assertIn(f"{job_model.class_path}: SUCCESS", out)
|
|
554
575
|
self.assertEqual("", err)
|
|
555
576
|
|
|
556
|
-
def
|
|
557
|
-
"""A job
|
|
558
|
-
|
|
559
|
-
Status.objects.get(name="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)
|
|
566
|
-
self.assertIn(f"Running {job_model.class_path}...", out)
|
|
567
|
-
self.assertIn(f"{module}: 1 success, 1 info, 0 warning, 0 failure", out)
|
|
568
|
-
self.assertIn("success: Test Status: Status created successfully.", out)
|
|
569
|
-
self.assertIn("info: Database changes have been reverted automatically.", out)
|
|
570
|
-
self.assertIn(f"{job_model.class_path}: SUCCESS", out)
|
|
571
|
-
self.assertEqual("", err)
|
|
572
|
-
|
|
573
|
-
with self.assertRaises(ObjectDoesNotExist):
|
|
574
|
-
Status.objects.get(name="test status")
|
|
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"
|
|
582
|
-
name = "TestModifyDB"
|
|
583
|
-
_job_class, job_model = get_job_class_and_model(module, name)
|
|
584
|
-
with self.assertRaises(CommandError):
|
|
585
|
-
self.run_command("--commit", job_model.class_path)
|
|
586
|
-
|
|
587
|
-
def test_runjob_db_change_commit_wrong_username(self):
|
|
588
|
-
"""A job that changes the DB, when run with commit=True and a nonexistent username, is rejected."""
|
|
589
|
-
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"
|
|
590
580
|
name = "TestModifyDB"
|
|
591
581
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
592
582
|
with self.assertRaises(CommandError):
|
|
593
|
-
self.run_command("--
|
|
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")
|
|
583
|
+
self.run_command("--username", "nosuchuser", job_model.class_path)
|
|
598
584
|
|
|
599
|
-
|
|
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
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,37 +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
684
|
with self.subTest(expected=False):
|
|
687
|
-
module = "
|
|
685
|
+
module = "pass"
|
|
688
686
|
name = "TestPass"
|
|
689
687
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
690
688
|
self.assertFalse(job_model.is_job_button_receiver)
|
|
691
689
|
|
|
692
690
|
with self.subTest(expected=True):
|
|
693
|
-
module = "
|
|
691
|
+
module = "job_button_receiver"
|
|
694
692
|
name = "TestJobButtonReceiverSimple"
|
|
695
693
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
696
694
|
self.assertTrue(job_model.is_job_button_receiver)
|
|
697
695
|
|
|
698
696
|
def test_missing_receive_job_button_method(self):
|
|
699
|
-
module = "
|
|
697
|
+
module = "job_button_receiver"
|
|
700
698
|
name = "TestJobButtonReceiverFail"
|
|
701
|
-
|
|
702
|
-
job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
|
|
703
|
-
logging.disable(logging.NOTSET)
|
|
699
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
704
700
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
705
701
|
|
|
706
702
|
|
|
@@ -720,53 +716,52 @@ class JobHookReceiverTest(TransactionTestCase):
|
|
|
720
716
|
# generate an ObjectChange by creating a new location
|
|
721
717
|
with web_request_context(self.user):
|
|
722
718
|
location_type = LocationType.objects.create(name="Test Root Type 1")
|
|
723
|
-
|
|
719
|
+
status = Status.objects.get_for_model(Location).first()
|
|
720
|
+
location = Location(name="Test Location 1", location_type=location_type, status=status)
|
|
724
721
|
location.save()
|
|
725
722
|
location.refresh_from_db()
|
|
726
723
|
oc = get_changes_for_model(location).first()
|
|
727
724
|
self.data = {"object_change": oc.id}
|
|
728
725
|
|
|
729
726
|
def test_form_field(self):
|
|
730
|
-
module = "
|
|
727
|
+
module = "job_hook_receiver"
|
|
731
728
|
name = "TestJobHookReceiverLog"
|
|
732
729
|
job_class, _job_model = get_job_class_and_model(module, name)
|
|
733
730
|
form = job_class().as_form()
|
|
734
|
-
self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "
|
|
731
|
+
self.assertSequenceEqual(list(form.fields.keys()), ["object_change", "_task_queue", "_profile"])
|
|
735
732
|
|
|
736
733
|
def test_hidden(self):
|
|
737
|
-
module = "
|
|
734
|
+
module = "job_hook_receiver"
|
|
738
735
|
name = "TestJobHookReceiverLog"
|
|
739
736
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
740
737
|
self.assertFalse(job_model.hidden)
|
|
741
738
|
|
|
742
739
|
def test_is_job_hook(self):
|
|
743
740
|
with self.subTest(expected=False):
|
|
744
|
-
module = "
|
|
741
|
+
module = "pass"
|
|
745
742
|
name = "TestPass"
|
|
746
743
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
747
744
|
self.assertFalse(job_model.is_job_hook_receiver)
|
|
748
745
|
|
|
749
746
|
with self.subTest(expected=True):
|
|
750
|
-
module = "
|
|
747
|
+
module = "job_hook_receiver"
|
|
751
748
|
name = "TestJobHookReceiverLog"
|
|
752
749
|
_job_class, job_model = get_job_class_and_model(module, name)
|
|
753
750
|
self.assertTrue(job_model.is_job_hook_receiver)
|
|
754
751
|
|
|
755
752
|
def test_object_change_context(self):
|
|
756
|
-
module = "
|
|
753
|
+
module = "job_hook_receiver"
|
|
757
754
|
name = "TestJobHookReceiverChange"
|
|
758
|
-
create_job_result_and_run_job(module, name,
|
|
755
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
759
756
|
test_location = Location.objects.get(name="test_jhr")
|
|
760
757
|
oc = get_changes_for_model(test_location).first()
|
|
761
758
|
self.assertEqual(oc.change_context, ObjectChangeEventContextChoices.CONTEXT_JOB_HOOK)
|
|
762
|
-
self.assertEqual(oc.user_id,
|
|
759
|
+
self.assertEqual(oc.user_id, job_result.user.pk)
|
|
763
760
|
|
|
764
761
|
def test_missing_receive_job_hook_method(self):
|
|
765
|
-
module = "
|
|
762
|
+
module = "job_hook_receiver"
|
|
766
763
|
name = "TestJobHookReceiverFail"
|
|
767
|
-
|
|
768
|
-
job_result = create_job_result_and_run_job(module, name, data=self.data, commit=False)
|
|
769
|
-
logging.disable(logging.NOTSET)
|
|
764
|
+
job_result = create_job_result_and_run_job(module, name, **self.data)
|
|
770
765
|
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
771
766
|
|
|
772
767
|
|
|
@@ -778,7 +773,7 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
778
773
|
def setUp(self):
|
|
779
774
|
super().setUp()
|
|
780
775
|
|
|
781
|
-
module = "
|
|
776
|
+
module = "job_hook_receiver"
|
|
782
777
|
name = "TestJobHookReceiverLog"
|
|
783
778
|
self.job_class, self.job_model = get_job_class_and_model(module, name)
|
|
784
779
|
job_hook = JobHook(
|
|
@@ -793,13 +788,16 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
793
788
|
|
|
794
789
|
def test_enqueue_job_hook(self):
|
|
795
790
|
with web_request_context(user=self.user):
|
|
796
|
-
|
|
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)
|
|
797
793
|
job_result = JobResult.objects.get(job_model=self.job_model)
|
|
798
794
|
expected_log_messages = [
|
|
795
|
+
("info", "Running job"),
|
|
799
796
|
("info", f"change: dcim | location Test Job Hook Location 1 created by {self.user.username}"),
|
|
800
797
|
("info", "action: create"),
|
|
801
|
-
("info", f"
|
|
802
|
-
("
|
|
798
|
+
("info", f"jobresult.user: {self.user.username}"),
|
|
799
|
+
("info", "Test Job Hook Location 1"),
|
|
800
|
+
("info", "Job completed"),
|
|
803
801
|
]
|
|
804
802
|
log_messages = JobLogEntry.objects.filter(job_result=job_result).values_list("log_level", "message")
|
|
805
803
|
self.assertSequenceEqual(log_messages, expected_log_messages)
|
|
@@ -807,8 +805,9 @@ class JobHookTest(TransactionTestCase): # TODO: BaseModelTestCase mixin?
|
|
|
807
805
|
@mock.patch.object(JobResult, "enqueue_job")
|
|
808
806
|
def test_enqueue_job_hook_skipped(self, mock_enqueue_job):
|
|
809
807
|
change_context = JobHookChangeContext(user=self.user)
|
|
808
|
+
status = Status.objects.get_for_model(Location).first()
|
|
810
809
|
with change_logging(change_context):
|
|
811
|
-
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)
|
|
812
811
|
|
|
813
812
|
self.assertFalse(mock_enqueue_job.called)
|
|
814
813
|
|
|
@@ -818,8 +817,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
818
817
|
for i in range(1, 7):
|
|
819
818
|
ScheduledJob.objects.create(
|
|
820
819
|
name=f"test{i}",
|
|
821
|
-
task="
|
|
822
|
-
job_class="
|
|
820
|
+
task="pass.TestPass",
|
|
821
|
+
job_class="pass.TestPass",
|
|
823
822
|
interval=JobExecutionType.TYPE_FUTURE,
|
|
824
823
|
user=self.user,
|
|
825
824
|
start_time=timezone.now() - datetime.timedelta(days=i * 30),
|
|
@@ -828,8 +827,8 @@ class RemoveScheduledJobManagementCommandTestCase(TestCase):
|
|
|
828
827
|
|
|
829
828
|
ScheduledJob.objects.create(
|
|
830
829
|
name="test7",
|
|
831
|
-
task="
|
|
832
|
-
job_class="
|
|
830
|
+
task="pass.TestPass",
|
|
831
|
+
job_class="pass.TestPass",
|
|
833
832
|
interval=JobExecutionType.TYPE_DAILY,
|
|
834
833
|
user=self.user,
|
|
835
834
|
start_time=timezone.now() - datetime.timedelta(days=180),
|
|
@@ -857,8 +856,8 @@ class ScheduledJobIntervalTestCase(TestCase):
|
|
|
857
856
|
start_time = timezone.now() + datetime.timedelta(days=6)
|
|
858
857
|
scheduled_job = ScheduledJob.objects.create(
|
|
859
858
|
name="weekly_interval",
|
|
860
|
-
task="
|
|
861
|
-
job_class="
|
|
859
|
+
task="pass.TestPass",
|
|
860
|
+
job_class="pass.TestPass",
|
|
862
861
|
interval=JobExecutionType.TYPE_WEEKLY,
|
|
863
862
|
user=self.user,
|
|
864
863
|
start_time=start_time,
|