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
nautobot/extras/models/jobs.py
CHANGED
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
# Data models relating to Jobs
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from datetime import timedelta
|
|
4
5
|
import logging
|
|
5
|
-
import os
|
|
6
|
-
import uuid
|
|
7
6
|
|
|
8
7
|
from celery import schedules
|
|
8
|
+
from celery.utils.log import get_logger, LoggingProxy
|
|
9
9
|
from django.conf import settings
|
|
10
10
|
from django.contrib.contenttypes.models import ContentType
|
|
11
|
-
from django.core.exceptions import
|
|
11
|
+
from django.core.exceptions import ValidationError
|
|
12
12
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
13
13
|
from django.core.validators import MinValueValidator
|
|
14
14
|
from django.db import models, transaction
|
|
15
15
|
from django.db.models import signals
|
|
16
|
-
from django.urls import reverse
|
|
17
16
|
from django.utils import timezone
|
|
17
|
+
from django.utils.functional import cached_property
|
|
18
18
|
from django_celery_beat.clockedschedule import clocked
|
|
19
19
|
from prometheus_client import Histogram
|
|
20
20
|
|
|
21
|
-
from nautobot.core.celery import
|
|
21
|
+
from nautobot.core.celery import (
|
|
22
|
+
add_nautobot_log_handler,
|
|
23
|
+
app,
|
|
24
|
+
NautobotKombuJSONEncoder,
|
|
25
|
+
setup_nautobot_job_logging,
|
|
26
|
+
)
|
|
27
|
+
from nautobot.core.celery.control import refresh_git_repository
|
|
22
28
|
from nautobot.core.models import BaseManager, BaseModel
|
|
23
|
-
from nautobot.core.models.fields import
|
|
29
|
+
from nautobot.core.models.fields import JSONArrayField
|
|
24
30
|
from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
|
|
25
31
|
from nautobot.core.utils.logging import sanitize
|
|
26
32
|
from nautobot.extras.choices import (
|
|
27
33
|
ButtonClassChoices,
|
|
28
34
|
JobExecutionType,
|
|
29
35
|
JobResultStatusChoices,
|
|
30
|
-
JobSourceChoices,
|
|
31
36
|
LogLevelChoices,
|
|
32
37
|
)
|
|
33
38
|
from nautobot.extras.constants import (
|
|
@@ -36,20 +41,15 @@ from nautobot.extras.constants import (
|
|
|
36
41
|
JOB_LOG_MAX_LOG_OBJECT_LENGTH,
|
|
37
42
|
JOB_MAX_GROUPING_LENGTH,
|
|
38
43
|
JOB_MAX_NAME_LENGTH,
|
|
39
|
-
JOB_MAX_SOURCE_LENGTH,
|
|
40
44
|
JOB_OVERRIDABLE_FIELDS,
|
|
41
45
|
)
|
|
42
|
-
from nautobot.extras.models import ChangeLoggedModel
|
|
46
|
+
from nautobot.extras.models import ChangeLoggedModel, GitRepository
|
|
43
47
|
from nautobot.extras.models.mixins import NotesMixin
|
|
44
|
-
from nautobot.extras.plugins.utils import import_object
|
|
45
48
|
from nautobot.extras.managers import JobResultManager, ScheduledJobsManager
|
|
46
49
|
from nautobot.extras.querysets import JobQuerySet, ScheduledJobExtendedQuerySet
|
|
47
50
|
from nautobot.extras.utils import (
|
|
48
51
|
ChangeLoggedModelsQuery,
|
|
49
|
-
FeatureQuery,
|
|
50
52
|
extras_features,
|
|
51
|
-
get_job_content_type,
|
|
52
|
-
jobs_in_directory,
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
from .customfields import CustomFieldModel
|
|
@@ -86,23 +86,6 @@ class Job(PrimaryModel):
|
|
|
86
86
|
"""
|
|
87
87
|
|
|
88
88
|
# Information used to locate the Job source code
|
|
89
|
-
source = models.CharField(
|
|
90
|
-
max_length=JOB_MAX_SOURCE_LENGTH,
|
|
91
|
-
choices=JobSourceChoices,
|
|
92
|
-
editable=False,
|
|
93
|
-
db_index=True,
|
|
94
|
-
help_text="Source of the Python code for this job - local, Git repository, or plugins",
|
|
95
|
-
)
|
|
96
|
-
git_repository = models.ForeignKey(
|
|
97
|
-
to="extras.GitRepository",
|
|
98
|
-
blank=True,
|
|
99
|
-
null=True,
|
|
100
|
-
default=None,
|
|
101
|
-
on_delete=models.SET_NULL,
|
|
102
|
-
db_index=True,
|
|
103
|
-
related_name="jobs",
|
|
104
|
-
help_text="Git repository that provides this job",
|
|
105
|
-
)
|
|
106
89
|
module_name = models.CharField(
|
|
107
90
|
max_length=JOB_MAX_NAME_LENGTH,
|
|
108
91
|
editable=False,
|
|
@@ -116,11 +99,6 @@ class Job(PrimaryModel):
|
|
|
116
99
|
help_text="Name of the Python class providing this job",
|
|
117
100
|
)
|
|
118
101
|
|
|
119
|
-
slug = AutoSlugField(
|
|
120
|
-
max_length=JOB_MAX_NAME_LENGTH,
|
|
121
|
-
populate_from="name",
|
|
122
|
-
)
|
|
123
|
-
|
|
124
102
|
# Human-readable information, potentially inherited from the source code
|
|
125
103
|
# See also the docstring of nautobot.extras.jobs.BaseJob.Meta.
|
|
126
104
|
grouping = models.CharField(
|
|
@@ -161,17 +139,17 @@ class Job(PrimaryModel):
|
|
|
161
139
|
approval_required = models.BooleanField(
|
|
162
140
|
default=False, help_text="Whether the job requires approval from another user before running"
|
|
163
141
|
)
|
|
164
|
-
commit_default = models.BooleanField(
|
|
165
|
-
default=True, help_text="Whether the job defaults to committing changes when run, or defaults to a dry-run"
|
|
166
|
-
)
|
|
167
142
|
hidden = models.BooleanField(
|
|
168
143
|
default=False,
|
|
169
144
|
db_index=True,
|
|
170
145
|
help_text="Whether the job defaults to not being shown in the UI",
|
|
171
146
|
)
|
|
172
147
|
# Job.Meta.field_order is not overridable in this model
|
|
148
|
+
dryrun_default = models.BooleanField(
|
|
149
|
+
default=False, help_text="Whether the job defaults to running with dryrun argument set to true"
|
|
150
|
+
)
|
|
173
151
|
read_only = models.BooleanField(
|
|
174
|
-
default=False, help_text="
|
|
152
|
+
default=False, editable=False, help_text="Set to true if the job does not make any changes to the environment"
|
|
175
153
|
)
|
|
176
154
|
soft_time_limit = models.FloatField(
|
|
177
155
|
default=0,
|
|
@@ -191,6 +169,11 @@ class Job(PrimaryModel):
|
|
|
191
169
|
blank=True,
|
|
192
170
|
help_text="Comma separated list of task queues that this job can run on. A blank list will use the default queue",
|
|
193
171
|
)
|
|
172
|
+
supports_dryrun = models.BooleanField(
|
|
173
|
+
default=False,
|
|
174
|
+
editable=False,
|
|
175
|
+
help_text="If supported, allows the job to bypass approval when running with dryrun argument set to true",
|
|
176
|
+
)
|
|
194
177
|
|
|
195
178
|
# Flags to indicate whether the above properties are inherited from the source code or overridden by the database
|
|
196
179
|
grouping_override = models.BooleanField(
|
|
@@ -205,12 +188,11 @@ class Job(PrimaryModel):
|
|
|
205
188
|
default=False,
|
|
206
189
|
help_text="If set, the configured description will remain even if the underlying Job source code changes",
|
|
207
190
|
)
|
|
208
|
-
|
|
209
191
|
approval_required_override = models.BooleanField(
|
|
210
192
|
default=False,
|
|
211
193
|
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
212
194
|
)
|
|
213
|
-
|
|
195
|
+
dryrun_default_override = models.BooleanField(
|
|
214
196
|
default=False,
|
|
215
197
|
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
216
198
|
)
|
|
@@ -218,10 +200,6 @@ class Job(PrimaryModel):
|
|
|
218
200
|
default=False,
|
|
219
201
|
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
220
202
|
)
|
|
221
|
-
read_only_override = models.BooleanField(
|
|
222
|
-
default=False,
|
|
223
|
-
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
224
|
-
)
|
|
225
203
|
soft_time_limit_override = models.BooleanField(
|
|
226
204
|
default=False,
|
|
227
205
|
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
@@ -244,90 +222,29 @@ class Job(PrimaryModel):
|
|
|
244
222
|
class Meta:
|
|
245
223
|
managed = True
|
|
246
224
|
ordering = ["grouping", "name"]
|
|
247
|
-
unique_together = ["
|
|
225
|
+
unique_together = ["module_name", "job_class_name"]
|
|
248
226
|
|
|
249
227
|
def __init__(self, *args, **kwargs):
|
|
250
228
|
super().__init__(*args, **kwargs)
|
|
251
|
-
self._job_class = None
|
|
252
229
|
self._latest_result = None
|
|
253
230
|
|
|
254
231
|
def __str__(self):
|
|
255
232
|
return self.name
|
|
256
233
|
|
|
257
|
-
|
|
258
|
-
"""
|
|
259
|
-
Check for duplicate (source, module_name, job_class_name) in the case where git_repository is None.
|
|
260
|
-
|
|
261
|
-
This is needed because NULL != NULL and so the unique_together constraint will not flag this case.
|
|
262
|
-
"""
|
|
263
|
-
if self.git_repository is None:
|
|
264
|
-
if Job.objects.exclude(pk=self.pk).filter(
|
|
265
|
-
source=self.source, module_name=self.module_name, job_class_name=self.job_class_name
|
|
266
|
-
):
|
|
267
|
-
raise ValidationError(
|
|
268
|
-
{"job_class_name": "A Job already exists with this source, module_name, and job_class_name"}
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
super().validate_unique(exclude=exclude)
|
|
272
|
-
|
|
273
|
-
@property
|
|
234
|
+
@cached_property
|
|
274
235
|
def job_class(self):
|
|
275
236
|
"""Get the Job class (source code) associated with this Job model."""
|
|
276
237
|
if not self.installed:
|
|
277
238
|
return None
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
self._job_class = job_info.job_class
|
|
284
|
-
break
|
|
285
|
-
else:
|
|
286
|
-
logger.warning("Module %s job class %s not found!", self.module_name, self.job_class_name)
|
|
287
|
-
elif self.source == JobSourceChoices.SOURCE_GIT:
|
|
288
|
-
from nautobot.extras.datasources.git import ensure_git_repository
|
|
289
|
-
|
|
290
|
-
if self.git_repository is None:
|
|
291
|
-
logger.warning("Job %s %s has no associated Git repository", self.module_name, self.job_class_name)
|
|
292
|
-
return None
|
|
293
|
-
try:
|
|
294
|
-
# In the case where we have multiple Nautobot instances, or multiple worker instances,
|
|
295
|
-
# they are not required to share a common filesystem; therefore, we may need to refresh our local
|
|
296
|
-
# clone of the Git repository to ensure that it is in sync with the latest repository clone
|
|
297
|
-
# from any instance.
|
|
298
|
-
ensure_git_repository(
|
|
299
|
-
self.git_repository,
|
|
300
|
-
head=self.git_repository.current_head,
|
|
301
|
-
logger=logger,
|
|
302
|
-
)
|
|
303
|
-
path = os.path.join(self.git_repository.filesystem_path, "jobs")
|
|
304
|
-
for job_info in jobs_in_directory(path, module_name=self.module_name):
|
|
305
|
-
if job_info.job_class_name == self.job_class_name:
|
|
306
|
-
self._job_class = job_info.job_class
|
|
307
|
-
break
|
|
308
|
-
else:
|
|
309
|
-
logger.warning(
|
|
310
|
-
"Module %s job class %s not found in repository %s",
|
|
311
|
-
self.module_name,
|
|
312
|
-
self.job_class_name,
|
|
313
|
-
self.git_repository,
|
|
314
|
-
)
|
|
315
|
-
except ObjectDoesNotExist:
|
|
316
|
-
return None
|
|
317
|
-
except Exception as exc:
|
|
318
|
-
logger.error(f"Error during local clone/refresh of Git repository {self.git_repository}: {exc}")
|
|
319
|
-
return None
|
|
320
|
-
elif self.source == JobSourceChoices.SOURCE_PLUGIN:
|
|
321
|
-
# pkgutil.resolve_name is only available in Python 3.9 and later
|
|
322
|
-
self._job_class = import_object(f"{self.module_name}.{self.job_class_name}")
|
|
323
|
-
|
|
324
|
-
return self._job_class
|
|
239
|
+
try:
|
|
240
|
+
return self.job_task.__class__
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
logger.error(str(exc))
|
|
243
|
+
return None
|
|
325
244
|
|
|
326
245
|
@property
|
|
327
246
|
def class_path(self):
|
|
328
|
-
|
|
329
|
-
return f"{self.source}.{self.git_repository.slug}/{self.module_name}/{self.job_class_name}"
|
|
330
|
-
return f"{self.source}/{self.module_name}/{self.job_class_name}"
|
|
247
|
+
return f"{self.module_name}.{self.job_class_name}"
|
|
331
248
|
|
|
332
249
|
@property
|
|
333
250
|
def latest_result(self):
|
|
@@ -341,12 +258,25 @@ class Job(PrimaryModel):
|
|
|
341
258
|
|
|
342
259
|
@property
|
|
343
260
|
def runnable(self):
|
|
344
|
-
return (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
261
|
+
return self.enabled and self.installed and not (self.has_sensitive_variables and self.approval_required)
|
|
262
|
+
|
|
263
|
+
@cached_property
|
|
264
|
+
def git_repository(self):
|
|
265
|
+
"""GitRepository record, if any, that owns this Job."""
|
|
266
|
+
try:
|
|
267
|
+
return GitRepository.objects.get(slug=self.module_name.split(".")[0])
|
|
268
|
+
except GitRepository.DoesNotExist:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def job_task(self):
|
|
273
|
+
"""Get the registered Celery task, refreshing it if necessary."""
|
|
274
|
+
if self.git_repository is not None:
|
|
275
|
+
# If this Job comes from a Git repository, make sure we have the correct version of said code.
|
|
276
|
+
refresh_git_repository(
|
|
277
|
+
state=None, repository_pk=self.git_repository.pk, head=self.git_repository.current_head
|
|
278
|
+
)
|
|
279
|
+
return app.tasks[f"{self.module_name}.{self.job_class_name}"]
|
|
350
280
|
|
|
351
281
|
def clean(self):
|
|
352
282
|
"""For any non-overridden fields, make sure they get reset to the actual underlying class value if known."""
|
|
@@ -355,12 +285,7 @@ class Job(PrimaryModel):
|
|
|
355
285
|
if not getattr(self, f"{field_name}_override", False):
|
|
356
286
|
setattr(self, field_name, getattr(self.job_class, field_name))
|
|
357
287
|
|
|
358
|
-
if self.git_repository is not None and self.source != JobSourceChoices.SOURCE_GIT:
|
|
359
|
-
raise ValidationError('A Git repository may only be specified when the source is "git"')
|
|
360
|
-
|
|
361
288
|
# Protect against invalid input when auto-creating Job records
|
|
362
|
-
if len(self.source) > JOB_MAX_SOURCE_LENGTH:
|
|
363
|
-
raise ValidationError(f"Source may not exceed {JOB_MAX_SOURCE_LENGTH} characters in length")
|
|
364
289
|
if len(self.module_name) > JOB_MAX_NAME_LENGTH:
|
|
365
290
|
raise ValidationError(f"Module name may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
|
|
366
291
|
if len(self.job_class_name) > JOB_MAX_NAME_LENGTH:
|
|
@@ -369,17 +294,12 @@ class Job(PrimaryModel):
|
|
|
369
294
|
raise ValidationError(f"Grouping may not exceed {JOB_MAX_GROUPING_LENGTH} characters in length")
|
|
370
295
|
if len(self.name) > JOB_MAX_NAME_LENGTH:
|
|
371
296
|
raise ValidationError(f"Name may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
|
|
372
|
-
if len(self.slug) > JOB_MAX_NAME_LENGTH:
|
|
373
|
-
raise ValidationError(f"Slug may not exceed {JOB_MAX_NAME_LENGTH} characters in length")
|
|
374
297
|
|
|
375
298
|
if self.has_sensitive_variables is True and self.approval_required is True:
|
|
376
299
|
raise ValidationError(
|
|
377
300
|
{"approval_required": "A job that may have sensitive variables cannot be marked as requiring approval"}
|
|
378
301
|
)
|
|
379
302
|
|
|
380
|
-
def get_absolute_url(self):
|
|
381
|
-
return reverse("extras:job_detail", kwargs={"slug": self.slug})
|
|
382
|
-
|
|
383
303
|
|
|
384
304
|
@extras_features("graphql")
|
|
385
305
|
class JobHook(OrganizationalModel):
|
|
@@ -423,9 +343,6 @@ class JobHook(OrganizationalModel):
|
|
|
423
343
|
if not self.type_create and not self.type_delete and not self.type_update:
|
|
424
344
|
raise ValidationError("You must select at least one type: create, update, and/or delete.")
|
|
425
345
|
|
|
426
|
-
def get_absolute_url(self):
|
|
427
|
-
return reverse("extras:jobhook", kwargs={"pk": self.pk})
|
|
428
|
-
|
|
429
346
|
@classmethod
|
|
430
347
|
def check_for_conflicts(
|
|
431
348
|
cls, instance=None, content_types=None, job=None, type_create=None, type_update=None, type_delete=None
|
|
@@ -484,7 +401,7 @@ class JobLogEntry(BaseModel):
|
|
|
484
401
|
|
|
485
402
|
job_result = models.ForeignKey(to="extras.JobResult", on_delete=models.CASCADE, related_name="job_log_entries")
|
|
486
403
|
log_level = models.CharField(
|
|
487
|
-
max_length=32, choices=LogLevelChoices, default=LogLevelChoices.
|
|
404
|
+
max_length=32, choices=LogLevelChoices, default=LogLevelChoices.LOG_INFO, db_index=True
|
|
488
405
|
)
|
|
489
406
|
grouping = models.CharField(max_length=JOB_LOG_MAX_GROUPING_LENGTH, default="main")
|
|
490
407
|
message = models.TextField(blank=True)
|
|
@@ -496,8 +413,6 @@ class JobLogEntry(BaseModel):
|
|
|
496
413
|
log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
|
|
497
414
|
absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
|
|
498
415
|
|
|
499
|
-
csv_headers = ["created", "grouping", "log_level", "log_object", "message"]
|
|
500
|
-
|
|
501
416
|
def __str__(self):
|
|
502
417
|
return self.message
|
|
503
418
|
|
|
@@ -506,10 +421,6 @@ class JobLogEntry(BaseModel):
|
|
|
506
421
|
get_latest_by = "created"
|
|
507
422
|
verbose_name_plural = "job log entries"
|
|
508
423
|
|
|
509
|
-
def to_csv(self):
|
|
510
|
-
"""Indicates model fields to return as csv."""
|
|
511
|
-
return (str(self.created), self.grouping, self.log_level, self.log_object, self.message)
|
|
512
|
-
|
|
513
424
|
|
|
514
425
|
#
|
|
515
426
|
# Job results
|
|
@@ -538,18 +449,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
538
449
|
db_index=True,
|
|
539
450
|
help_text="Registered name of the Celery task for this job. Internal use only.",
|
|
540
451
|
)
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
related_name="job_results",
|
|
544
|
-
verbose_name="Object types",
|
|
545
|
-
limit_choices_to=FeatureQuery("job_results"),
|
|
546
|
-
help_text="The object type to which this job result applies",
|
|
547
|
-
on_delete=models.CASCADE,
|
|
548
|
-
null=True,
|
|
549
|
-
blank=True,
|
|
550
|
-
)
|
|
551
|
-
date_created = models.DateTimeField(auto_now_add=True)
|
|
552
|
-
date_done = models.DateTimeField(null=True, blank=True)
|
|
452
|
+
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
453
|
+
date_done = models.DateTimeField(null=True, blank=True, db_index=True)
|
|
553
454
|
user = models.ForeignKey(
|
|
554
455
|
to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="+", blank=True, null=True
|
|
555
456
|
)
|
|
@@ -563,23 +464,16 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
563
464
|
data = models.JSONField(encoder=DjangoJSONEncoder, null=True, blank=True)
|
|
564
465
|
"""
|
|
565
466
|
Although "data" is technically an unstructured field, we have a standard structure that we try to adhere to.
|
|
566
|
-
|
|
567
467
|
This structure is created loosely as a superset of the formats used by Scripts and Reports in NetBox 2.10.
|
|
568
|
-
|
|
569
468
|
Log Messages now go to their own object, the JobLogEntry.
|
|
570
|
-
|
|
571
469
|
data = {
|
|
572
470
|
"output": <optional string, such as captured stdout/stderr>,
|
|
573
471
|
}
|
|
574
472
|
"""
|
|
575
|
-
periodic_task_name = models.CharField(
|
|
576
|
-
null=True,
|
|
577
|
-
max_length=255,
|
|
578
|
-
help_text="Registered name of the Celery periodic task for this job. Internal use only.",
|
|
579
|
-
)
|
|
580
473
|
worker = models.CharField(max_length=100, default=None, null=True)
|
|
581
|
-
task_args = models.JSONField(blank=True,
|
|
582
|
-
task_kwargs = models.JSONField(blank=True,
|
|
474
|
+
task_args = models.JSONField(blank=True, default=list, encoder=NautobotKombuJSONEncoder)
|
|
475
|
+
task_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
|
|
476
|
+
celery_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
|
|
583
477
|
# TODO(jathan): This field is currently unused for Jobs, but we should coerce it to a JSONField
|
|
584
478
|
# and set a contract that anything returned from a Job task MUST be JSON. In DCR core it is
|
|
585
479
|
# expected to be encoded/decoded using `content_type` and `content_encoding` which we have
|
|
@@ -595,8 +489,6 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
595
489
|
meta = models.JSONField(null=True, default=None, editable=False)
|
|
596
490
|
scheduled_job = models.ForeignKey(to="extras.ScheduledJob", on_delete=models.SET_NULL, null=True, blank=True)
|
|
597
491
|
|
|
598
|
-
task_id = models.UUIDField(unique=True)
|
|
599
|
-
|
|
600
492
|
objects = JobResultManager()
|
|
601
493
|
|
|
602
494
|
def __init__(self, *args, **kwargs):
|
|
@@ -606,14 +498,34 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
606
498
|
class Meta:
|
|
607
499
|
ordering = ["-date_created"]
|
|
608
500
|
get_latest_by = "date_created"
|
|
501
|
+
indexes = [
|
|
502
|
+
models.Index(
|
|
503
|
+
name="extras_jobresult_rcreated_idx",
|
|
504
|
+
fields=["-date_created"],
|
|
505
|
+
),
|
|
506
|
+
models.Index(
|
|
507
|
+
name="extras_jr_rdone_idx",
|
|
508
|
+
fields=["-date_done"],
|
|
509
|
+
),
|
|
510
|
+
models.Index(
|
|
511
|
+
name="extras_jr_statrcreate_idx",
|
|
512
|
+
fields=["status", "-date_created"],
|
|
513
|
+
),
|
|
514
|
+
models.Index(
|
|
515
|
+
name="extras_jr_statrdone_idx",
|
|
516
|
+
fields=["status", "-date_done"],
|
|
517
|
+
),
|
|
518
|
+
]
|
|
519
|
+
|
|
520
|
+
natural_key_field_names = ["id"]
|
|
609
521
|
|
|
610
522
|
def __str__(self):
|
|
611
|
-
return
|
|
523
|
+
return f"{self.name} started at {self.date_created} ({self.status})"
|
|
612
524
|
|
|
613
525
|
def as_dict(self):
|
|
614
526
|
"""This is required by the django-celery-results DB backend."""
|
|
615
527
|
return {
|
|
616
|
-
"
|
|
528
|
+
"id": self.id,
|
|
617
529
|
"task_name": self.task_name,
|
|
618
530
|
"task_args": self.task_args,
|
|
619
531
|
"task_kwargs": self.task_kwargs,
|
|
@@ -635,92 +547,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
635
547
|
|
|
636
548
|
return f"{int(minutes)} minutes, {seconds:.2f} seconds"
|
|
637
549
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
"""Get the related object, if any, identified by the `obj_type`, `name`, and/or `task_id` fields.
|
|
641
|
-
|
|
642
|
-
If `obj_type` is extras.Job, then the `name` is used to look up an extras.jobs.Job subclass based on the
|
|
643
|
-
`class_path` of the Job subclass.
|
|
644
|
-
Note that this is **not** the extras.models.Job model class nor an instance thereof.
|
|
645
|
-
|
|
646
|
-
Else, if the the model class referenced by `obj_type` has a `name` field, our `name` field will be used
|
|
647
|
-
to look up a corresponding model instance. This is used, for example, to look up a related `GitRepository`;
|
|
648
|
-
more generally it can be used by any model that 1) has a unique `name` field and 2) needs to have a many-to-one
|
|
649
|
-
relationship between JobResults and model instances.
|
|
650
|
-
|
|
651
|
-
Else, the `obj_type` and `task_id` will be used together as a quasi-GenericForeignKey to look up a model
|
|
652
|
-
instance whose PK corresponds to the `task_id`. This behavior is currently unused in the Nautobot core,
|
|
653
|
-
but may be of use to plugin developers wishing to create JobResults that have a one-to-one relationship
|
|
654
|
-
to plugin model instances.
|
|
655
|
-
|
|
656
|
-
This method is potentially rather slow as get_job() may need to actually load the Job class from disk;
|
|
657
|
-
consider carefully whether you actually need to use it.
|
|
658
|
-
"""
|
|
659
|
-
from nautobot.extras.jobs import get_job # needed here to avoid a circular import issue
|
|
660
|
-
|
|
661
|
-
if self.obj_type == get_job_content_type():
|
|
662
|
-
# Related object is an extras.Job subclass, our `name` matches its `class_path`
|
|
663
|
-
return get_job(self.name)
|
|
664
|
-
|
|
665
|
-
if self.obj_type:
|
|
666
|
-
model_class = self.obj_type.model_class()
|
|
667
|
-
else:
|
|
668
|
-
model_class = None
|
|
669
|
-
|
|
670
|
-
if model_class is not None:
|
|
671
|
-
if hasattr(model_class, "name"):
|
|
672
|
-
# See if we have a many-to-one relationship from JobResult to model_class record, based on `name`
|
|
673
|
-
try:
|
|
674
|
-
return model_class.objects.get(name=self.name)
|
|
675
|
-
except model_class.DoesNotExist:
|
|
676
|
-
pass
|
|
677
|
-
|
|
678
|
-
# See if we have a one-to-one relationship from JobResult to model_class record based on `task_id`
|
|
679
|
-
try:
|
|
680
|
-
return model_class.objects.get(id=self.task_id)
|
|
681
|
-
except model_class.DoesNotExist:
|
|
682
|
-
pass
|
|
683
|
-
|
|
684
|
-
return None
|
|
685
|
-
|
|
686
|
-
@property
|
|
687
|
-
def related_name(self):
|
|
688
|
-
"""
|
|
689
|
-
Similar to self.name, but if there's an appropriate `related_object`, use its name instead.
|
|
690
|
-
|
|
691
|
-
Since this calls related_object, the same potential performance concerns exist. Use with caution.
|
|
692
|
-
"""
|
|
693
|
-
related_object = self.related_object
|
|
694
|
-
if not related_object:
|
|
695
|
-
return self.name
|
|
696
|
-
if hasattr(related_object, "name"):
|
|
697
|
-
return related_object.name
|
|
698
|
-
return str(related_object)
|
|
699
|
-
|
|
700
|
-
@property
|
|
701
|
-
def linked_record(self):
|
|
702
|
-
"""
|
|
703
|
-
A newer alternative to self.related_object that looks up an extras.models.Job instead of an extras.jobs.Job.
|
|
704
|
-
"""
|
|
705
|
-
if self.job_model is not None:
|
|
706
|
-
return self.job_model
|
|
707
|
-
model_class = self.obj_type.model_class()
|
|
708
|
-
if model_class is not None:
|
|
709
|
-
if hasattr(model_class, "name"):
|
|
710
|
-
try:
|
|
711
|
-
return model_class.objects.get(name=self.name)
|
|
712
|
-
except model_class.DoesNotExist:
|
|
713
|
-
pass
|
|
714
|
-
if hasattr(model_class, "class_path"):
|
|
715
|
-
try:
|
|
716
|
-
return model_class.objects.get(class_path=self.name)
|
|
717
|
-
except model_class.DoesNotExist:
|
|
718
|
-
pass
|
|
719
|
-
return None
|
|
720
|
-
|
|
721
|
-
def get_absolute_url(self):
|
|
722
|
-
return reverse("extras:jobresult", kwargs={"pk": self.pk})
|
|
723
|
-
|
|
550
|
+
# FIXME(jathan): This needs to go away. Need to think about that the impact
|
|
551
|
+
# will be in the JOB_RESULT_METRIC and how to compensate for it.
|
|
724
552
|
def set_status(self, status):
|
|
725
553
|
"""
|
|
726
554
|
Helper method to change the status of the job result. If the target status is terminal, the completion
|
|
@@ -738,69 +566,118 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
738
566
|
)
|
|
739
567
|
|
|
740
568
|
@classmethod
|
|
741
|
-
def
|
|
569
|
+
def execute_job(cls, job_model, user, *job_args, celery_kwargs=None, profile=False, **job_kwargs):
|
|
742
570
|
"""
|
|
743
|
-
Create a JobResult instance and
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
571
|
+
Create a JobResult instance and run a job in the current process, blocking until the job finishes.
|
|
572
|
+
|
|
573
|
+
Running tasks synchronously in celery is *NOT* supported and if possible `enqueue_job` with synchronous=False
|
|
574
|
+
should be used instead.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
job_model (Job): The Job to be enqueued for execution
|
|
578
|
+
user (User): User object to link to the JobResult instance
|
|
579
|
+
celery_kwargs (dict, optional): Dictionary of kwargs to pass as **kwargs to Celery when job is run
|
|
580
|
+
profile (bool, optional): Whether to run cProfile on the job execution
|
|
581
|
+
*job_args: positional args passed to the job task
|
|
582
|
+
**job_kwargs: keyword args passed to the job task
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
JobResult instance
|
|
586
|
+
"""
|
|
587
|
+
return cls.enqueue_job(
|
|
588
|
+
job_model, user, *job_args, celery_kwargs=celery_kwargs, profile=profile, synchronous=True, **job_kwargs
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
@classmethod
|
|
592
|
+
def enqueue_job(
|
|
593
|
+
cls,
|
|
594
|
+
job_model,
|
|
595
|
+
user,
|
|
596
|
+
*job_args,
|
|
597
|
+
celery_kwargs=None,
|
|
598
|
+
profile=False,
|
|
599
|
+
schedule=None,
|
|
600
|
+
task_queue=None,
|
|
601
|
+
synchronous=False,
|
|
602
|
+
**job_kwargs,
|
|
603
|
+
):
|
|
604
|
+
"""Create a JobResult instance and enqueue a job to be executed asynchronously by a Celery worker.
|
|
605
|
+
|
|
606
|
+
Args:
|
|
607
|
+
job_model (Job): The Job to be enqueued for execution.
|
|
608
|
+
user (User): User object to link to the JobResult instance.
|
|
609
|
+
celery_kwargs (dict, optional): Dictionary of kwargs to pass as **kwargs to `apply_async()`/`apply()` when job is run.
|
|
610
|
+
profile (bool, optional): If True, dump cProfile stats on the job execution.
|
|
611
|
+
schedule (ScheduledJob, optional): ScheduledJob instance to link to the JobResult. Cannot be used with synchronous=True.
|
|
612
|
+
task_queue (str, optional): The celery queue to send the job to. If not set, use the default celery queue.
|
|
613
|
+
synchronous (bool, optional): If True, run the job in the current process, blocking until the job completes.
|
|
614
|
+
*job_args: positional args passed to the job task
|
|
615
|
+
**job_kwargs: keyword args passed to the job task
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
JobResult instance
|
|
754
619
|
"""
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
620
|
+
if schedule is not None and synchronous:
|
|
621
|
+
raise ValueError("Scheduled jobs cannot be run synchronously")
|
|
622
|
+
|
|
758
623
|
job_result = cls.objects.create(
|
|
759
|
-
name=name,
|
|
760
|
-
|
|
761
|
-
user=user,
|
|
762
|
-
task_id=uuid.uuid4(),
|
|
624
|
+
name=job_model.name,
|
|
625
|
+
job_model=job_model,
|
|
763
626
|
scheduled_job=schedule,
|
|
627
|
+
user=user,
|
|
764
628
|
)
|
|
765
629
|
|
|
766
|
-
|
|
630
|
+
if task_queue is None:
|
|
631
|
+
task_queue = settings.CELERY_TASK_DEFAULT_QUEUE
|
|
767
632
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
633
|
+
job_celery_kwargs = {
|
|
634
|
+
"nautobot_job_job_model_id": job_model.id,
|
|
635
|
+
"nautobot_job_profile": profile,
|
|
636
|
+
"nautobot_job_user_id": user.id,
|
|
637
|
+
"queue": task_queue,
|
|
638
|
+
}
|
|
771
639
|
|
|
772
|
-
if
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
640
|
+
if schedule is not None:
|
|
641
|
+
job_celery_kwargs["nautobot_job_schedule_id"] = schedule.id
|
|
642
|
+
if job_model.soft_time_limit > 0:
|
|
643
|
+
job_celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
|
|
644
|
+
if job_model.time_limit > 0:
|
|
645
|
+
job_celery_kwargs["time_limit"] = job_model.time_limit
|
|
646
|
+
|
|
647
|
+
if celery_kwargs is not None:
|
|
648
|
+
job_celery_kwargs.update(celery_kwargs)
|
|
649
|
+
|
|
650
|
+
if synchronous:
|
|
651
|
+
# synchronous tasks are run before the JobResult is saved, so any fields required by
|
|
652
|
+
# the job must be added before calling `apply()`
|
|
653
|
+
job_result.celery_kwargs = job_celery_kwargs
|
|
654
|
+
job_result.save()
|
|
655
|
+
|
|
656
|
+
# setup synchronous task logging
|
|
657
|
+
redirect_logger = get_logger("celery.redirected")
|
|
658
|
+
add_nautobot_log_handler(redirect_logger)
|
|
659
|
+
setup_nautobot_job_logging(None, None, app.conf)
|
|
660
|
+
|
|
661
|
+
# redirect stdout/stderr to logger and run task
|
|
662
|
+
proxy = LoggingProxy(redirect_logger, app.conf.worker_redirect_stdouts_level)
|
|
663
|
+
with contextlib.redirect_stdout(proxy), contextlib.redirect_stderr(proxy):
|
|
664
|
+
eager_result = job_model.job_task.apply(
|
|
665
|
+
args=job_args, kwargs=job_kwargs, task_id=str(job_result.id), **job_celery_kwargs
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
# copy fields from eager result to job result
|
|
669
|
+
job_result.refresh_from_db()
|
|
670
|
+
for field in ["status", "result", "traceback", "worker"]:
|
|
671
|
+
setattr(job_result, field, getattr(eager_result, field, None))
|
|
672
|
+
job_result.date_done = timezone.now()
|
|
673
|
+
job_result.save()
|
|
674
|
+
else:
|
|
675
|
+
# Jobs queued inside of a transaction need to run after the transaction completes and the JobResult is saved to the database
|
|
676
|
+
transaction.on_commit(
|
|
677
|
+
lambda: job_model.job_task.apply_async(
|
|
678
|
+
args=job_args, kwargs=job_kwargs, task_id=str(job_result.id), **job_celery_kwargs
|
|
679
|
+
)
|
|
680
|
+
)
|
|
804
681
|
|
|
805
682
|
return job_result
|
|
806
683
|
|
|
@@ -808,7 +685,7 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
808
685
|
self,
|
|
809
686
|
message,
|
|
810
687
|
obj=None,
|
|
811
|
-
level_choice=LogLevelChoices.
|
|
688
|
+
level_choice=LogLevelChoices.LOG_INFO,
|
|
812
689
|
grouping="main",
|
|
813
690
|
logger=None, # pylint: disable=redefined-outer-name
|
|
814
691
|
):
|
|
@@ -848,12 +725,7 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
848
725
|
log.save(using=JOB_LOGS)
|
|
849
726
|
|
|
850
727
|
if logger:
|
|
851
|
-
|
|
852
|
-
log_level = logging.ERROR
|
|
853
|
-
elif level_choice == LogLevelChoices.LOG_WARNING:
|
|
854
|
-
log_level = logging.WARNING
|
|
855
|
-
else:
|
|
856
|
-
log_level = logging.INFO
|
|
728
|
+
log_level = getattr(logging, level_choice.upper(), logging.INFO)
|
|
857
729
|
logger.log(log_level, message)
|
|
858
730
|
|
|
859
731
|
|
|
@@ -908,9 +780,6 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
|
|
|
908
780
|
def __str__(self):
|
|
909
781
|
return self.name
|
|
910
782
|
|
|
911
|
-
def get_absolute_url(self):
|
|
912
|
-
return reverse("extras:jobbutton", kwargs={"pk": self.pk})
|
|
913
|
-
|
|
914
783
|
|
|
915
784
|
class ScheduledJobs(models.Model):
|
|
916
785
|
"""Helper table for tracking updates to scheduled tasks.
|
|
@@ -976,6 +845,7 @@ class ScheduledJob(BaseModel):
|
|
|
976
845
|
interval = models.CharField(choices=JobExecutionType, max_length=255)
|
|
977
846
|
args = models.JSONField(blank=True, default=list, encoder=NautobotKombuJSONEncoder)
|
|
978
847
|
kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
|
|
848
|
+
celery_kwargs = models.JSONField(blank=True, default=dict, encoder=NautobotKombuJSONEncoder)
|
|
979
849
|
queue = models.CharField(
|
|
980
850
|
max_length=200,
|
|
981
851
|
blank=True,
|
|
@@ -1061,13 +931,11 @@ class ScheduledJob(BaseModel):
|
|
|
1061
931
|
def __str__(self):
|
|
1062
932
|
return f"{self.name}: {self.interval}"
|
|
1063
933
|
|
|
1064
|
-
|
|
1065
|
-
|
|
934
|
+
# TODO: there's currently no natural key for ScheduledJob
|
|
935
|
+
natural_key_field_names = ["id"]
|
|
1066
936
|
|
|
1067
937
|
def save(self, *args, **kwargs):
|
|
1068
938
|
self.queue = self.queue or ""
|
|
1069
|
-
# pass pk to worker task in kwargs, celery doesn't provide the full object to the worker
|
|
1070
|
-
self.kwargs["scheduled_job_pk"] = self.pk
|
|
1071
939
|
# make sure non-valid crontab doesn't get saved
|
|
1072
940
|
if self.interval == JobExecutionType.TYPE_CUSTOM:
|
|
1073
941
|
try:
|