nautobot 2.4.21__py3-none-any.whl → 3.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/choices.py +2 -2
- nautobot/apps/filters.py +9 -9
- nautobot/apps/forms.py +2 -0
- nautobot/apps/models.py +7 -2
- nautobot/apps/ui.py +20 -1
- nautobot/apps/utils.py +2 -3
- nautobot/apps/views.py +7 -1
- nautobot/circuits/filters.py +8 -23
- nautobot/circuits/navigation.py +3 -1
- nautobot/circuits/templates/circuits/circuit_create.html +9 -9
- nautobot/circuits/templates/circuits/circuit_terminations_swap.html +2 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +24 -33
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +10 -10
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +13 -13
- nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +6 -6
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +3 -3
- nautobot/circuits/templates/circuits/inc/speed_widget.html +13 -13
- nautobot/circuits/templates/circuits/provider_create.html +9 -9
- nautobot/circuits/tests/integration/test_circuit.py +19 -19
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +3 -0
- nautobot/circuits/tests/integration/test_relationships.py +4 -12
- nautobot/circuits/views.py +0 -2
- nautobot/cloud/filters.py +1 -13
- nautobot/cloud/navigation.py +3 -1
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +9 -9
- nautobot/cloud/templates/cloud/cloudservice_update.html +6 -6
- nautobot/core/api/fields.py +30 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +9 -2
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +58 -37
- nautobot/core/apps/__init__.py +6 -12
- nautobot/core/branching.py +83 -0
- nautobot/core/celery/__init__.py +11 -6
- nautobot/core/celery/backends.py +2 -0
- nautobot/core/celery/encoders.py +7 -0
- nautobot/core/celery/task.py +44 -0
- nautobot/core/checks.py +60 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +776 -0
- nautobot/core/constants.py +9 -0
- nautobot/core/context_processors.py +84 -0
- nautobot/core/filters.py +131 -2
- nautobot/core/forms/__init__.py +4 -2
- nautobot/core/forms/fields.py +10 -8
- nautobot/core/forms/forms.py +21 -9
- nautobot/core/forms/search.py +0 -15
- nautobot/core/forms/widgets.py +3 -2
- nautobot/core/graphql/__init__.py +8 -26
- nautobot/core/graphql/generators.py +16 -6
- nautobot/core/graphql/schema.py +1 -1
- nautobot/core/graphql/schema_init.py +1 -2
- nautobot/core/graphql/utils.py +7 -9
- nautobot/core/jobs/__init__.py +158 -0
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +17 -2
- nautobot/core/models/fields.py +3 -2
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/name_color_content_types.py +1 -1
- nautobot/core/models/ordering.py +7 -5
- nautobot/core/models/querysets.py +77 -2
- nautobot/core/models/tree_queries.py +6 -4
- nautobot/core/settings.py +30 -16
- nautobot/core/settings.yaml +13 -7
- nautobot/core/tables.py +114 -44
- nautobot/core/templates/403.html +1 -1
- nautobot/core/templates/403_csrf_failure.html +1 -1
- nautobot/core/templates/404.html +1 -1
- nautobot/core/templates/40x.html +8 -8
- nautobot/core/templates/500.html +10 -10
- nautobot/core/templates/about.html +13 -12
- nautobot/core/templates/admin/actions.html +1 -1
- nautobot/core/templates/admin/app_index.html +3 -3
- nautobot/core/templates/admin/base.html +45 -52
- nautobot/core/templates/admin/base_site.html +0 -9
- nautobot/core/templates/admin/change_form.html +5 -5
- nautobot/core/templates/admin/change_list.html +8 -12
- nautobot/core/templates/admin/change_list_results.html +3 -3
- nautobot/core/templates/admin/config/config.html +24 -24
- nautobot/core/templates/admin/delete_confirmation.html +5 -5
- nautobot/core/templates/admin/edit_inline/stacked.html +5 -5
- nautobot/core/templates/admin/edit_inline/tabular.html +3 -3
- nautobot/core/templates/admin/includes/fieldset.html +15 -15
- nautobot/core/templates/admin/index.html +8 -8
- nautobot/core/templates/admin/submit_line.html +5 -5
- nautobot/core/templates/base_django.html +36 -32
- nautobot/core/templates/buttons/add.html +1 -1
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +2 -2
- nautobot/core/templates/buttons/export.html +17 -18
- nautobot/core/templates/buttons/job_import.html +2 -2
- nautobot/core/templates/components/breadcrumbs.html +19 -17
- nautobot/core/templates/components/button/dropdown.html +7 -5
- nautobot/core/templates/components/echarts.html +2 -0
- nautobot/core/templates/components/layout/one_over_two.html +3 -3
- nautobot/core/templates/components/layout/two_over_one.html +3 -3
- nautobot/core/templates/components/panel/body_content_data_table.html +2 -2
- nautobot/core/templates/components/panel/body_content_tags.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_generic.html +4 -2
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_table.html +4 -2
- nautobot/core/templates/components/panel/footer_contacts_table.html +4 -4
- nautobot/core/templates/components/panel/footer_content_table.html +3 -3
- nautobot/core/templates/components/panel/grouping_toggle.html +12 -11
- nautobot/core/templates/components/panel/header_extra_content_table.html +2 -11
- nautobot/core/templates/components/panel/panel.html +6 -3
- nautobot/core/templates/components/panel/stats_panel_body.html +9 -7
- nautobot/core/templates/components/tab/content_wrapper.html +29 -1
- nautobot/core/templates/components/tab/label_wrapper.html +10 -2
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +11 -4
- nautobot/core/templates/echarts/echarts.html +20 -0
- nautobot/core/templates/exceptions/import_error.html +2 -2
- nautobot/core/templates/exceptions/permission_error.html +1 -1
- nautobot/core/templates/exceptions/programming_error.html +2 -2
- nautobot/core/templates/generic/object_bulk_add_component.html +29 -20
- nautobot/core/templates/generic/object_bulk_create.html +87 -75
- nautobot/core/templates/generic/object_bulk_destroy.html +35 -37
- nautobot/core/templates/generic/object_bulk_remove.html +30 -26
- nautobot/core/templates/generic/object_bulk_rename.html +53 -40
- nautobot/core/templates/generic/object_bulk_update.html +36 -29
- nautobot/core/templates/generic/object_create.html +40 -27
- nautobot/core/templates/generic/object_import.html +36 -24
- nautobot/core/templates/generic/object_list.html +279 -215
- nautobot/core/templates/generic/object_notes.html +21 -11
- nautobot/core/templates/generic/object_retrieve.html +161 -213
- nautobot/core/templates/graphene/graphiql.html +113 -60
- nautobot/core/templates/home.html +164 -87
- nautobot/core/templates/import_success.html +3 -2
- nautobot/core/templates/inc/ajax_loader.html +1 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +25 -13
- nautobot/core/templates/inc/created_updated.html +12 -7
- nautobot/core/templates/inc/custom_fields/panel_data.html +28 -16
- nautobot/core/templates/inc/custom_fields_panel.html +3 -3
- nautobot/core/templates/inc/dynamic_groups_panel.html +3 -3
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +15 -15
- nautobot/core/templates/inc/footer.html +90 -40
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +75 -0
- nautobot/core/templates/inc/header_banners.html +17 -0
- nautobot/core/templates/inc/header_messages.html +6 -0
- nautobot/core/templates/inc/image_attachments.html +9 -9
- nautobot/core/templates/inc/javascript.html +7 -24
- nautobot/core/templates/inc/media.html +4 -29
- nautobot/core/templates/inc/modal.html +2 -2
- nautobot/core/templates/inc/nav_favorites.html +27 -0
- nautobot/core/templates/inc/nav_menu.html +150 -108
- nautobot/core/templates/inc/object_details_advanced_panel.html +84 -71
- nautobot/core/templates/inc/page_title.html +23 -0
- nautobot/core/templates/inc/paginator.html +39 -28
- nautobot/core/templates/inc/relationships/panel_override.html +3 -3
- nautobot/core/templates/inc/relationships_panel.html +3 -3
- nautobot/core/templates/inc/relationships_table_rows.html +1 -1
- nautobot/core/templates/inc/search_panel.html +22 -16
- nautobot/core/templates/inc/table.html +61 -36
- nautobot/core/templates/inc/tenancy_form_panel.html +3 -3
- nautobot/core/templates/login.html +17 -59
- nautobot/core/templates/modals/modal_theme.html +12 -23
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/panel_table.html +8 -12
- nautobot/core/templates/redoc_ui.html +80 -0
- nautobot/core/templates/rest_framework/api.html +43 -21
- nautobot/core/templates/search.html +12 -13
- nautobot/core/templates/swagger_ui.html +19 -4
- nautobot/core/templates/system_jobs/import_objects.html +70 -58
- nautobot/core/templates/template.css +0 -6
- nautobot/core/templates/utilities/comment_form.html +34 -0
- nautobot/core/templates/utilities/confirmation_form.html +17 -9
- nautobot/core/templates/utilities/obj_table.html +19 -11
- nautobot/core/templates/utilities/render_field.html +27 -21
- nautobot/core/templates/utilities/render_jinja2.html +22 -25
- nautobot/core/templates/utilities/templatetags/advanced_filter_indicator.html +8 -0
- nautobot/core/templates/utilities/templatetags/badge.html +1 -1
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +2 -3
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +482 -0
- nautobot/core/templates/utilities/templatetags/modal_form_as_dialog.html +14 -18
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +11 -11
- nautobot/core/templates/utilities/templatetags/table_config_form.html +51 -24
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/templatetags/utilization_graph.html +3 -3
- nautobot/core/templates/utilities/theme_preview.html +829 -566
- nautobot/core/templates/utilities/worker_status.html +42 -41
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templates/widgets/sluginput.html +2 -2
- nautobot/core/templatetags/buttons.py +38 -40
- nautobot/core/templatetags/helpers.py +105 -28
- nautobot/core/templatetags/ui_framework.py +17 -0
- nautobot/core/testing/api.py +76 -12
- nautobot/core/testing/filters.py +11 -27
- nautobot/core/testing/integration.py +128 -10
- nautobot/core/testing/mixins.py +7 -4
- nautobot/core/testing/utils.py +28 -5
- nautobot/core/testing/views.py +125 -27
- nautobot/core/tests/integration/test_app_home.py +39 -35
- nautobot/core/tests/integration/test_app_navbar.py +60 -67
- nautobot/core/tests/integration/test_filters.py +123 -55
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_home.py +10 -18
- nautobot/core/tests/integration/test_import_objects_ui.py +2 -9
- nautobot/core/tests/integration/test_navbar.py +41 -16
- nautobot/core/tests/integration/test_swagger.py +1 -7
- nautobot/core/tests/integration/test_theme.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +6 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_branching.py +154 -0
- nautobot/core/tests/test_breadcrumbs.py +7 -8
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_commands.py +0 -41
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_csv.py +4 -7
- nautobot/core/tests/test_filters.py +326 -318
- nautobot/core/tests/test_forms.py +19 -30
- nautobot/core/tests/test_graphql.py +67 -57
- nautobot/core/tests/test_models.py +1 -1
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +78 -10
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +61 -21
- nautobot/core/tests/test_templatetags_ui_framework.py +36 -18
- nautobot/core/tests/test_ui.py +207 -2
- nautobot/core/tests/test_utils.py +147 -2
- nautobot/core/tests/test_views.py +201 -64
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +190 -0
- nautobot/core/ui/constants.py +86 -0
- nautobot/core/ui/echarts.py +474 -0
- nautobot/core/ui/nav.py +5 -1
- nautobot/core/ui/object_detail.py +180 -16
- nautobot/core/urls.py +13 -1
- nautobot/core/utils/cache.py +71 -0
- nautobot/core/utils/data.py +8 -5
- nautobot/core/utils/filtering.py +8 -2
- nautobot/core/utils/git.py +3 -3
- nautobot/core/utils/lookup.py +87 -13
- nautobot/core/utils/migrations.py +22 -0
- nautobot/core/utils/module_loading.py +26 -0
- nautobot/core/utils/permissions.py +9 -5
- nautobot/core/views/__init__.py +114 -63
- nautobot/core/views/generic.py +34 -27
- nautobot/core/views/mixins.py +49 -27
- nautobot/core/views/renderers.py +3 -5
- nautobot/core/views/utils.py +10 -5
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/__init__.py +0 -0
- nautobot/data_validation/api/__init__.py +1 -0
- nautobot/data_validation/api/serializers.py +80 -0
- nautobot/data_validation/api/urls.py +20 -0
- nautobot/data_validation/api/views.py +44 -0
- nautobot/data_validation/apps.py +18 -0
- nautobot/data_validation/custom_validators.py +330 -0
- nautobot/data_validation/filters.py +133 -0
- nautobot/data_validation/form_mixin.py +25 -0
- nautobot/data_validation/forms.py +342 -0
- nautobot/data_validation/migrations/0001_initial.py +224 -0
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +324 -0
- nautobot/data_validation/migrations/__init__.py +0 -0
- nautobot/data_validation/models.py +361 -0
- nautobot/data_validation/navigation.py +74 -0
- nautobot/data_validation/signals.py +30 -0
- nautobot/data_validation/tables.py +259 -0
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +1 -0
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +11 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +20 -0
- nautobot/data_validation/tests/migrations/__init__.py +0 -0
- nautobot/data_validation/tests/migrations/test_migrations.py +489 -0
- nautobot/data_validation/tests/test_api.py +238 -0
- nautobot/data_validation/tests/test_custom_validators.py +423 -0
- nautobot/data_validation/tests/test_data_compliance_rules.py +85 -0
- nautobot/data_validation/tests/test_filters.py +240 -0
- nautobot/data_validation/tests/test_form_mixin.py +115 -0
- nautobot/data_validation/tests/test_models.py +393 -0
- nautobot/data_validation/tests/test_views.py +435 -0
- nautobot/data_validation/urls.py +21 -0
- nautobot/data_validation/views.py +227 -0
- nautobot/dcim/api/serializers.py +10 -13
- nautobot/dcim/api/urls.py +2 -0
- nautobot/dcim/api/views.py +7 -0
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +16 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +70 -157
- nautobot/dcim/forms.py +12 -6
- nautobot/dcim/graphql/types.py +1 -0
- nautobot/dcim/migrations/0075_add_deviceclusterassignment.py +52 -0
- nautobot/dcim/migrations/0076_device_cluster_to_clusters_data_migration.py +40 -0
- nautobot/dcim/migrations/0077_remove_device_cluster.py +14 -0
- nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
- nautobot/dcim/models/__init__.py +2 -0
- nautobot/dcim/models/device_components.py +3 -1
- nautobot/dcim/models/devices.py +115 -51
- nautobot/dcim/navigation.py +7 -3
- nautobot/dcim/querysets.py +6 -0
- nautobot/dcim/signals.py +19 -0
- nautobot/dcim/tables/devices.py +9 -5
- nautobot/dcim/tables/template_code.py +191 -102
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_connect.html +62 -146
- nautobot/dcim/templates/dcim/cable_retrieve.html +10 -10
- nautobot/dcim/templates/dcim/cable_trace.html +15 -17
- nautobot/dcim/templates/dcim/console_port_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/consoleport.html +18 -17
- nautobot/dcim/templates/dcim/consoleserverport.html +18 -17
- nautobot/dcim/templates/dcim/controller_create.html +12 -8
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +1 -1
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +6 -6
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/device/config.html +17 -19
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +4 -4
- nautobot/dcim/templates/dcim/device/status.html +20 -20
- nautobot/dcim/templates/dcim/device_component_add.html +24 -15
- nautobot/dcim/templates/dcim/device_create.html +120 -120
- nautobot/dcim/templates/dcim/device_list.html +75 -12
- nautobot/dcim/templates/dcim/devicebay.html +7 -7
- nautobot/dcim/templates/dcim/devicebay_populate.html +29 -23
- nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +6 -6
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/devicetype_component_add.html +25 -19
- nautobot/dcim/templates/dcim/devicetype_list.html +4 -4
- nautobot/dcim/templates/dcim/devicetype_update.html +9 -9
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +3 -3
- nautobot/dcim/templates/dcim/frontport.html +21 -20
- nautobot/dcim/templates/dcim/inc/cable_form.html +7 -7
- nautobot/dcim/templates/dcim/inc/cable_termination.html +1 -1
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +18 -9
- nautobot/dcim/templates/dcim/inc/detail_softwareversion_softwareimagefile_rows.html +1 -1
- nautobot/dcim/templates/dcim/inc/device_interface_filter.html +1 -1
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/homepage_connections.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +42 -22
- nautobot/dcim/templates/dcim/interface_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/interface_edit.html +26 -11
- nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem_add.html +21 -10
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +6 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +24 -18
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/location_update.html +9 -9
- nautobot/dcim/templates/dcim/locationtype.html +0 -1
- nautobot/dcim/templates/dcim/module/base.html +67 -27
- nautobot/dcim/templates/dcim/module_consoleports.html +13 -15
- nautobot/dcim/templates/dcim/module_consoleserverports.html +13 -15
- nautobot/dcim/templates/dcim/module_frontports.html +13 -15
- nautobot/dcim/templates/dcim/module_interfaces.html +14 -16
- nautobot/dcim/templates/dcim/module_list.html +59 -10
- nautobot/dcim/templates/dcim/module_modulebays.html +12 -14
- nautobot/dcim/templates/dcim/module_poweroutlets.html +13 -15
- nautobot/dcim/templates/dcim/module_powerports.html +13 -15
- nautobot/dcim/templates/dcim/module_rearports.html +13 -15
- nautobot/dcim/templates/dcim/module_retrieve.html +3 -3
- nautobot/dcim/templates/dcim/module_update.html +15 -9
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -93
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +7 -7
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +74 -35
- nautobot/dcim/templates/dcim/platform_create.html +9 -9
- nautobot/dcim/templates/dcim/power_port_connection_list.html +3 -3
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed_edit.html +15 -15
- nautobot/dcim/templates/dcim/poweroutlet.html +13 -13
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +17 -16
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/rack_elevation_list.html +21 -9
- nautobot/dcim/templates/dcim/rack_retrieve.html +75 -57
- nautobot/dcim/templates/dcim/rack_update.html +14 -14
- nautobot/dcim/templates/dcim/rackreservation.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation_edit.html +6 -6
- nautobot/dcim/templates/dcim/rearport.html +19 -18
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/trace/circuit.html +1 -1
- nautobot/dcim/templates/dcim/trace/device.html +1 -1
- nautobot/dcim/templates/dcim/trace/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/trace/termination.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +25 -16
- nautobot/dcim/templates/dcim/virtualchassis_create.html +6 -6
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_update.html +36 -22
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +9 -9
- nautobot/dcim/tests/integration/test_controller.py +6 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +7 -7
- nautobot/dcim/tests/integration/test_create_device.py +9 -9
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +7 -2
- nautobot/dcim/tests/integration/test_fileinputpicker.py +5 -7
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +2 -0
- nautobot/dcim/tests/integration/test_module_bay_position.py +4 -1
- nautobot/dcim/tests/test_api.py +86 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +159 -110
- nautobot/dcim/tests/test_graphql.py +32 -36
- nautobot/dcim/tests/test_jobs.py +1 -1
- nautobot/dcim/tests/test_models.py +229 -1
- nautobot/dcim/tests/test_views.py +31 -20
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +77 -41
- nautobot/extras/api/serializers.py +83 -19
- nautobot/extras/api/urls.py +7 -0
- nautobot/extras/api/views.py +243 -140
- nautobot/extras/choices.py +34 -13
- nautobot/extras/constants.py +1 -1
- nautobot/extras/context_managers.py +26 -26
- nautobot/extras/datasources/git.py +22 -0
- nautobot/extras/datasources/registry.py +3 -0
- nautobot/extras/exceptions.py +5 -0
- nautobot/extras/factory.py +11 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +4 -3
- nautobot/extras/{filters/__init__.py → filters.py} +203 -58
- nautobot/extras/forms/base.py +2 -1
- nautobot/extras/forms/forms.py +225 -20
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/homepage.py +21 -2
- nautobot/extras/jobs.py +2 -8
- nautobot/extras/jobs_ui.py +2 -2
- nautobot/extras/management/__init__.py +9 -0
- nautobot/extras/managers.py +31 -22
- nautobot/extras/migrations/0126_approval_workflow_pre_check.py +58 -0
- nautobot/extras/migrations/0127_approval_workflow_models.py +266 -0
- nautobot/extras/migrations/0128_remove_job_approval_required_and_more.py +29 -0
- nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
- nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
- nautobot/extras/models/__init__.py +14 -3
- nautobot/extras/models/approvals.py +556 -0
- nautobot/extras/models/change_logging.py +1 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/customfields.py +57 -22
- nautobot/extras/models/datasources.py +21 -0
- nautobot/extras/models/groups.py +2 -0
- nautobot/extras/models/jobs.py +122 -39
- nautobot/extras/models/metadata.py +2 -3
- nautobot/extras/models/mixins.py +129 -1
- nautobot/extras/models/models.py +22 -14
- nautobot/extras/models/relationships.py +47 -10
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/models/tags.py +1 -1
- nautobot/extras/navigation.py +42 -15
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +1 -23
- nautobot/extras/plugins/tables.py +8 -6
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/validators.py +10 -10
- nautobot/extras/plugins/views.py +1 -5
- nautobot/extras/querysets.py +17 -21
- nautobot/extras/signals.py +23 -8
- nautobot/extras/tables.py +420 -99
- nautobot/extras/templates/extras/approval_dashboard.html +15 -0
- nautobot/extras/templates/extras/approval_workflow/approve.html +11 -0
- nautobot/extras/templates/extras/approval_workflow/comment.html +9 -0
- nautobot/extras/templates/extras/approval_workflow/deny.html +10 -0
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +77 -0
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +29 -0
- nautobot/extras/templates/extras/configcontext_update.html +12 -12
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +9 -9
- nautobot/extras/templates/extras/configcontextschema_update.html +6 -6
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield_update.html +12 -12
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_edit.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +17 -17
- nautobot/extras/templates/extras/dynamicgroup_update.html +24 -24
- nautobot/extras/templates/extras/externalintegration_update.html +6 -6
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/gitrepository_object_edit.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +1 -1
- nautobot/extras/templates/extras/gitrepository_retrieve.html +12 -12
- nautobot/extras/templates/extras/gitrepository_update.html +25 -7
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -1
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +38 -0
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +14 -13
- nautobot/extras/templates/extras/inc/configcontext_format.html +11 -4
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +7 -7
- nautobot/extras/templates/extras/inc/job_label.html +5 -5
- nautobot/extras/templates/extras/inc/job_table.html +23 -10
- nautobot/extras/templates/extras/inc/job_tiles.html +33 -21
- nautobot/extras/templates/extras/inc/jobresult.html +6 -6
- nautobot/extras/templates/extras/inc/json_format.html +11 -4
- nautobot/extras/templates/extras/inc/object_contact_header.html +6 -6
- nautobot/extras/templates/extras/inc/overridable_field.html +16 -15
- nautobot/extras/templates/extras/inc/panel_approvalworkflowstage.html +34 -0
- nautobot/extras/templates/extras/inc/panel_changelog.html +9 -9
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +8 -6
- nautobot/extras/templates/extras/inc/tags_panel.html +3 -3
- nautobot/extras/templates/extras/job.html +154 -155
- nautobot/extras/templates/extras/job_approval_confirmation.html +4 -27
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -1
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/job_edit.html +69 -64
- nautobot/extras/templates/extras/job_list.html +37 -60
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/jobresult_retrieve.html +17 -17
- nautobot/extras/templates/extras/marketplace.html +62 -71
- nautobot/extras/templates/extras/metadatatype_create.html +9 -9
- nautobot/extras/templates/extras/note.html +1 -1
- nautobot/extras/templates/extras/object_approvalworkflow.html +36 -0
- nautobot/extras/templates/extras/object_assign_contact_or_team.html +16 -7
- nautobot/extras/templates/extras/object_configcontext.html +20 -20
- nautobot/extras/templates/extras/object_new_contact.html +6 -6
- nautobot/extras/templates/extras/object_new_team.html +6 -6
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/objectchange_retrieve.html +37 -56
- nautobot/extras/templates/extras/plugin_detail.html +40 -41
- nautobot/extras/templates/extras/plugins_list.html +23 -38
- nautobot/extras/templates/extras/plugins_tiles.html +28 -28
- nautobot/extras/templates/extras/role_retrieve.html +112 -48
- nautobot/extras/templates/extras/scheduledjob.html +25 -28
- nautobot/extras/templates/extras/secret_create.html +11 -11
- nautobot/extras/templates/extras/secretsgroup_update.html +6 -6
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +3 -3
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +3 -3
- nautobot/extras/templates/extras/templatetags/log_level.html +1 -1
- nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -2
- nautobot/extras/templates/extras/webhook.html +12 -12
- nautobot/extras/templatetags/approvals.py +19 -0
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_computedfields.py +5 -8
- nautobot/extras/tests/integration/test_configcontextschema.py +43 -48
- nautobot/extras/tests/integration/test_customfields.py +33 -33
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -10
- nautobot/extras/tests/integration/test_jobs.py +2 -4
- nautobot/extras/tests/integration/test_notes.py +3 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +35 -27
- nautobot/extras/tests/integration/test_relationships.py +7 -11
- nautobot/extras/tests/integration/test_tagfilter.py +3 -11
- nautobot/extras/tests/test_api.py +786 -242
- nautobot/extras/tests/test_approvals.py +715 -0
- nautobot/extras/tests/test_changelog.py +18 -14
- nautobot/extras/tests/test_customfields.py +14 -13
- nautobot/extras/tests/test_datasources.py +1 -1
- nautobot/extras/tests/test_dynamicgroups.py +9 -4
- nautobot/extras/tests/test_filters.py +443 -13
- nautobot/extras/tests/test_forms.py +18 -57
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +81 -1
- nautobot/extras/tests/test_models.py +378 -47
- nautobot/extras/tests/test_plugins.py +47 -13
- nautobot/extras/tests/test_relationships.py +7 -2
- nautobot/extras/tests/test_utils.py +2 -0
- nautobot/extras/tests/test_views.py +780 -493
- nautobot/extras/urls.py +36 -12
- nautobot/extras/utils.py +58 -12
- nautobot/extras/views.py +668 -209
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +35 -71
- nautobot/ipam/formfields.py +1 -1
- nautobot/ipam/forms.py +6 -3
- nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
- nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
- nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/navigation.py +3 -1
- nautobot/ipam/querysets.py +1 -2
- nautobot/ipam/tables.py +26 -17
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +6 -6
- nautobot/ipam/templates/ipam/inc/service.html +8 -8
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +3 -2
- nautobot/ipam/templates/ipam/ipaddress.html +27 -13
- nautobot/ipam/templates/ipam/ipaddress_assign.html +31 -24
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +3 -3
- nautobot/ipam/templates/ipam/ipaddress_edit.html +9 -9
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddress_merge.html +195 -186
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +7 -5
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_create.html +9 -9
- nautobot/ipam/templates/ipam/prefix_list.html +15 -14
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_update.html +6 -6
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/templates/ipam/vrf_edit.html +15 -15
- nautobot/ipam/tests/integration/test_prefixes.py +5 -13
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +20 -7
- nautobot/ipam/tests/test_filters.py +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +24 -21
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +49 -7
- nautobot/project-static/dist/css/graphql-libraries.css +655 -0
- nautobot/project-static/dist/css/graphql-libraries.css.map +1 -0
- nautobot/project-static/dist/css/materialdesignicons.css +3 -0
- nautobot/project-static/dist/css/materialdesignicons.css.map +1 -0
- nautobot/project-static/dist/css/nautobot.css +13 -0
- nautobot/project-static/dist/css/nautobot.css.map +1 -0
- nautobot/project-static/dist/js/graphql-libraries.js +3 -0
- nautobot/project-static/dist/js/graphql-libraries.js.LICENSE.txt +62 -0
- nautobot/project-static/dist/js/graphql-libraries.js.map +1 -0
- nautobot/project-static/dist/js/libraries.js +3 -0
- nautobot/project-static/dist/js/libraries.js.LICENSE.txt +65 -0
- nautobot/project-static/dist/js/libraries.js.map +1 -0
- nautobot/project-static/dist/js/materialdesignicons.js +0 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js +2 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -0
- nautobot/project-static/dist/js/nautobot.js +2 -0
- nautobot/project-static/dist/js/nautobot.js.map +1 -0
- nautobot/project-static/fonts/Montserrat-v30-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Light.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Regular.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Light.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Regular.woff2 +0 -0
- nautobot/project-static/img/jinja_logo.svg +21 -92
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/editor.js +4 -4
- nautobot/project-static/js/forms.js +67 -717
- nautobot/project-static/js/job_result.js +2 -2
- nautobot/project-static/nautobot-icons/360-degrees.svg +3 -0
- nautobot/project-static/nautobot-icons/arrow-decision.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-expand-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-2-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/atom.svg +3 -0
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/branch.svg +3 -0
- nautobot/project-static/nautobot-icons/briefcase-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data.svg +3 -0
- nautobot/project-static/nautobot-icons/cast.svg +3 -0
- nautobot/project-static/nautobot-icons/check-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-check.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-upload.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +3 -0
- nautobot/project-static/nautobot-icons/compass.svg +3 -0
- nautobot/project-static/nautobot-icons/control-panel.svg +3 -0
- nautobot/project-static/nautobot-icons/credit-card.svg +3 -0
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +3 -0
- nautobot/project-static/nautobot-icons/direction.svg +3 -0
- nautobot/project-static/nautobot-icons/elements.svg +3 -0
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/globe-2.svg +3 -0
- nautobot/project-static/nautobot-icons/globe.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +3 -0
- nautobot/project-static/nautobot-icons/history.svg +3 -0
- nautobot/project-static/nautobot-icons/ip.svg +3 -0
- nautobot/project-static/nautobot-icons/laptop.svg +3 -0
- nautobot/project-static/nautobot-icons/lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/list-unordered.svg +3 -0
- nautobot/project-static/nautobot-icons/map-view.svg +3 -0
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-2.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-3.svg +3 -0
- nautobot/project-static/nautobot-icons/plug.svg +3 -0
- nautobot/project-static/nautobot-icons/refresh-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/rocket-2.svg +3 -0
- nautobot/project-static/nautobot-icons/rotate-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/route.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +3 -0
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server-2.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +3 -0
- nautobot/project-static/nautobot-icons/share.svg +3 -0
- nautobot/project-static/nautobot-icons/shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/sitemap-outline.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert-2.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert.svg +3 -0
- nautobot/project-static/nautobot-icons/star-filled.svg +3 -0
- nautobot/project-static/nautobot-icons/star.svg +3 -0
- nautobot/project-static/nautobot-icons/transform.svg +3 -0
- nautobot/project-static/nautobot-icons/wifi.svg +3 -0
- nautobot/tenancy/api/serializers.py +1 -0
- nautobot/tenancy/api/views.py +2 -1
- nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
- nautobot/tenancy/navigation.py +3 -1
- nautobot/tenancy/templates/tenancy/tenant_create.html +6 -6
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/.gitignore +137 -0
- nautobot/ui/.node-version +1 -0
- nautobot/ui/.prettierignore +3 -0
- nautobot/ui/eslint.config.js +33 -0
- nautobot/ui/package-lock.json +6594 -0
- nautobot/ui/package.json +67 -0
- nautobot/ui/prettier.config.js +9 -0
- nautobot/ui/src/js/collapse.js +69 -0
- nautobot/ui/src/js/cookie.js +31 -0
- nautobot/ui/src/js/draggable.js +101 -0
- nautobot/ui/src/js/drawer.js +106 -0
- nautobot/ui/src/js/form.js +23 -0
- nautobot/ui/src/js/history.js +51 -0
- nautobot/ui/src/js/nautobot-graphiql.js +19 -0
- nautobot/ui/src/js/nautobot.js +128 -0
- nautobot/ui/src/js/search.js +274 -0
- nautobot/ui/src/js/select2.js +318 -0
- nautobot/ui/src/js/sidenav.js +87 -0
- nautobot/ui/src/js/tabs.js +139 -0
- nautobot/ui/src/js/theme.js +104 -0
- nautobot/ui/src/js/utils.js +54 -0
- nautobot/ui/src/scss/colors.scss +58 -0
- nautobot/ui/src/scss/nautobot.scss +2471 -0
- nautobot/ui/webpack.config.js +148 -0
- nautobot/users/apps.py +3 -0
- nautobot/users/filters.py +7 -11
- nautobot/users/forms.py +10 -0
- nautobot/users/models.py +8 -0
- nautobot/users/templates/users/advanced_settings_edit.html +31 -21
- nautobot/users/templates/users/api_tokens.html +61 -51
- nautobot/users/templates/users/base.html +23 -31
- nautobot/users/templates/users/change_password.html +29 -19
- nautobot/users/templates/users/preferences.html +55 -45
- nautobot/users/templates/users/profile.html +45 -14
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/urls.py +2 -0
- nautobot/users/views.py +70 -2
- nautobot/virtualization/api/views.py +1 -1
- nautobot/virtualization/filters.py +18 -32
- nautobot/virtualization/forms.py +22 -59
- nautobot/virtualization/models.py +1 -19
- nautobot/virtualization/navigation.py +3 -1
- nautobot/virtualization/tables.py +10 -6
- nautobot/virtualization/templates/virtualization/cluster.html +13 -13
- nautobot/virtualization/templates/virtualization/cluster_edit.html +6 -6
- nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +24 -16
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +4 -4
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +27 -25
- nautobot/virtualization/templates/virtualization/vminterface.html +5 -5
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +27 -11
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_models.py +20 -5
- nautobot/virtualization/tests/test_views.py +3 -5
- nautobot/virtualization/urls.py +0 -11
- nautobot/virtualization/views.py +5 -122
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +209 -0
- nautobot/vpn/filters.py +233 -0
- nautobot/vpn/forms.py +486 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +527 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +380 -0
- nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +341 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +294 -0
- nautobot/vpn/tests/test_models.py +97 -0
- nautobot/vpn/tests/test_views.py +281 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +437 -0
- nautobot/wireless/filters.py +0 -8
- nautobot/wireless/navigation.py +3 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +6 -6
- nautobot/wireless/tests/integration/test_radio_profile.py +3 -7
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +5 -4
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +802 -707
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/core/management/commands/check_job_approval_status.py +0 -47
- nautobot/core/templates/search_form.html +0 -9
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +0 -87
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/extras/templates/extras/job_approval_request.html +0 -134
- nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +0 -28
- nautobot/ipam/mixins.py +0 -32
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +0 -587
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +0 -6865
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +0 -288
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.js +0 -2580
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/js/npm.js +0 -13
- nautobot/project-static/clipboard.js-2.0.9/clipboard.min.js +0 -7
- nautobot/project-static/css/base.css +0 -1040
- nautobot/project-static/css/dark.css +0 -282
- nautobot/project-static/flatpickr-4.6.9/flatpickr.min.js +0 -2
- nautobot/project-static/flatpickr-4.6.9/themes/light.min.css +0 -1
- nautobot/project-static/graphiql-1.5.16/graphiql.min.css +0 -12
- nautobot/project-static/graphiql-1.5.16/graphiql.min.js +0 -11
- nautobot/project-static/highlight.js-11.9.0/github-dark.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/github.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/highlight.min.js +0 -378
- nautobot/project-static/jquery/jquery-3.7.1.min.js +0 -2
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_444444_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_555555_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777620_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777777_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_cc0000_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_ffffff_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +0 -7
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +0 -6
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +0 -5
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.theme.min.css +0 -5
- nautobot/project-static/js/homepage_layout.js +0 -182
- nautobot/project-static/js/nav_menu.js +0 -250
- nautobot/project-static/js/theme.js +0 -133
- nautobot/project-static/materialdesignicons-7.4.47/LICENSE +0 -20
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/react-16.14.0/react.production.min.js +0 -32
- nautobot/project-static/react-dom-16.14.0/react-dom.production.min.js +0 -239
- nautobot/project-static/select2-4.0.13/i18n/af.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ar.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/az.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bg.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bn.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ca.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/cs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/da.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/de.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/dsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/el.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/en.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/es.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/et.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/eu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fa.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/gl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/he.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hy.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/id.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/is.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/it.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ja.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ka.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/km.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ko.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/mk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ms.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ne.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ps.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt-BR.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ro.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ru.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sq.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr-Cyrl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/th.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/uk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/vi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-CN.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-TW.js +0 -3
- nautobot/project-static/select2-4.0.13/select2.min.css +0 -1
- nautobot/project-static/select2-4.0.13/select2.min.js +0 -2
- nautobot/project-static/select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css +0 -7
- nautobot/project-static/subscriptions-transport-ws-0.9.18/client.min.js +0 -8
- nautobot/project-static/whatwg-fetch-3.6.2/fetch.umd.min.js +0 -8
- nautobot/virtualization/templates/virtualization/cluster_add_devices.html +0 -37
- /nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.ttf → dist/1fcc36272ea3e53d0031.ttf} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.eot → dist/2146c3c82b553977abc7.eot} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff → dist/e55a20c80650829ec5fd.woff} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 → dist/ec024da790d2972da002.woff2} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
nautobot/extras/api/views.py
CHANGED
|
@@ -6,7 +6,7 @@ from django.http import FileResponse, Http404
|
|
|
6
6
|
from django.shortcuts import get_object_or_404
|
|
7
7
|
from django.utils import timezone
|
|
8
8
|
from drf_spectacular.types import OpenApiTypes
|
|
9
|
-
from drf_spectacular.utils import extend_schema, extend_schema_view
|
|
9
|
+
from drf_spectacular.utils import extend_schema, extend_schema_view
|
|
10
10
|
from graphene_django.views import GraphQLView
|
|
11
11
|
from graphql import GraphQLError
|
|
12
12
|
from rest_framework import mixins, status, viewsets
|
|
@@ -27,16 +27,19 @@ from nautobot.core.api.views import (
|
|
|
27
27
|
NautobotAPIVersionMixin,
|
|
28
28
|
ReadOnlyModelViewSet,
|
|
29
29
|
)
|
|
30
|
-
from nautobot.core.events import publish_event
|
|
31
30
|
from nautobot.core.exceptions import CeleryWorkerNotRunningException
|
|
32
31
|
from nautobot.core.graphql import execute_saved_query
|
|
33
32
|
from nautobot.core.models.querysets import count_related
|
|
34
|
-
from nautobot.core.models.utils import serialize_object_v2
|
|
35
33
|
from nautobot.extras import filters
|
|
36
|
-
from nautobot.extras.choices import JobExecutionType, JobQueueTypeChoices
|
|
34
|
+
from nautobot.extras.choices import ApprovalWorkflowStateChoices, JobExecutionType, JobQueueTypeChoices
|
|
37
35
|
from nautobot.extras.filters import RoleFilterSet
|
|
38
36
|
from nautobot.extras.jobs import get_job
|
|
39
37
|
from nautobot.extras.models import (
|
|
38
|
+
ApprovalWorkflow,
|
|
39
|
+
ApprovalWorkflowDefinition,
|
|
40
|
+
ApprovalWorkflowStage,
|
|
41
|
+
ApprovalWorkflowStageDefinition,
|
|
42
|
+
ApprovalWorkflowStageResponse,
|
|
40
43
|
ComputedField,
|
|
41
44
|
ConfigContext,
|
|
42
45
|
ConfigContextSchema,
|
|
@@ -256,6 +259,194 @@ class NautobotModelViewSet(NotesViewSetMixin, CustomFieldModelViewSet):
|
|
|
256
259
|
"""
|
|
257
260
|
|
|
258
261
|
|
|
262
|
+
#
|
|
263
|
+
# Approval Workflows
|
|
264
|
+
#
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class ApprovalWorkflowDefinitionViewSet(NautobotModelViewSet):
|
|
268
|
+
"""ApprovalWorkflowDefinition viewset."""
|
|
269
|
+
|
|
270
|
+
queryset = ApprovalWorkflowDefinition.objects.all()
|
|
271
|
+
serializer_class = serializers.ApprovalWorkflowDefinitionSerializer
|
|
272
|
+
filterset_class = filters.ApprovalWorkflowDefinitionFilterSet
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class ApprovalWorkflowStageDefinitionViewSet(NautobotModelViewSet):
|
|
276
|
+
"""ApprovalWorkflowStageDefinition viewset."""
|
|
277
|
+
|
|
278
|
+
queryset = ApprovalWorkflowStageDefinition.objects.all()
|
|
279
|
+
serializer_class = serializers.ApprovalWorkflowStageDefinitionSerializer
|
|
280
|
+
filterset_class = filters.ApprovalWorkflowStageDefinitionFilterSet
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ApprovalWorkflowViewSet(NautobotModelViewSet):
|
|
284
|
+
"""ApprovalWorkflow viewset."""
|
|
285
|
+
|
|
286
|
+
queryset = ApprovalWorkflow.objects.all()
|
|
287
|
+
serializer_class = serializers.ApprovalWorkflowSerializer
|
|
288
|
+
filterset_class = filters.ApprovalWorkflowFilterSet
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class ApprovalWorkflowStageViewSet(NautobotModelViewSet):
|
|
292
|
+
"""ApprovalWorkflowStage viewset."""
|
|
293
|
+
|
|
294
|
+
queryset = ApprovalWorkflowStage.objects.all()
|
|
295
|
+
serializer_class = serializers.ApprovalWorkflowStageSerializer
|
|
296
|
+
filterset_class = filters.ApprovalWorkflowStageFilterSet
|
|
297
|
+
|
|
298
|
+
def _validate_stage(self, stage):
|
|
299
|
+
"""Checks if a workflow has an active stage."""
|
|
300
|
+
return stage.state == ApprovalWorkflowStateChoices.PENDING
|
|
301
|
+
|
|
302
|
+
def _has_change_permission(self, user, workflow):
|
|
303
|
+
"""Checks whether the user has 'change' permission on the model under review."""
|
|
304
|
+
ct = workflow.object_under_review_content_type
|
|
305
|
+
model_name = ct.model
|
|
306
|
+
app_label = ct.app_label
|
|
307
|
+
change_perm = f"{app_label}.change_{model_name}"
|
|
308
|
+
return user.has_perm(change_perm)
|
|
309
|
+
|
|
310
|
+
def _is_user_approver(self, user, stage):
|
|
311
|
+
"""Checks if the user belongs to the group allowed to approve the current stage."""
|
|
312
|
+
approver_group = stage.approval_workflow_stage_definition.approver_group
|
|
313
|
+
return user in approver_group.user_set.all()
|
|
314
|
+
|
|
315
|
+
def _user_already_approved_or_denied(self, user, stage, action_type):
|
|
316
|
+
"""Checks if the user has already approved/denied to the current stage."""
|
|
317
|
+
if action_type == "approve":
|
|
318
|
+
return user in stage.users_that_already_approved
|
|
319
|
+
elif action_type == "deny":
|
|
320
|
+
return user in stage.users_that_already_denied
|
|
321
|
+
else:
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def _handle_approve_deny_response(self, request, action_type):
|
|
325
|
+
"""Common logic for approve/deny actions."""
|
|
326
|
+
stage = self.get_object()
|
|
327
|
+
user = request.user
|
|
328
|
+
if not self._validate_stage(stage):
|
|
329
|
+
return Response(
|
|
330
|
+
{"detail": "Approval workflow stage is not active."},
|
|
331
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
332
|
+
)
|
|
333
|
+
workflow = stage.approval_workflow
|
|
334
|
+
if not self._has_change_permission(user, workflow):
|
|
335
|
+
ct = workflow.object_under_review_content_type
|
|
336
|
+
model_name = ct.model
|
|
337
|
+
app_label = ct.app_label
|
|
338
|
+
return Response(
|
|
339
|
+
{"detail": f"You do not have 'change' permission on {app_label}.{model_name}."},
|
|
340
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if not self._is_user_approver(user, stage):
|
|
344
|
+
return Response(
|
|
345
|
+
{"detail": "You do not have permission to approve this stage."},
|
|
346
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if self._user_already_approved_or_denied(user, stage, action_type):
|
|
350
|
+
past_tense = {"approve": "approved", "deny": "denied"}.get(action_type, action_type)
|
|
351
|
+
return Response(
|
|
352
|
+
{"detail": f"You have already {past_tense} to this stage."},
|
|
353
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
comment = request.data.get("comments", "")
|
|
357
|
+
ApprovalWorkflowStageResponse.objects.create(
|
|
358
|
+
approval_workflow_stage=stage,
|
|
359
|
+
user=user,
|
|
360
|
+
state=ApprovalWorkflowStateChoices.APPROVED
|
|
361
|
+
if action_type == "approve"
|
|
362
|
+
else ApprovalWorkflowStateChoices.DENIED,
|
|
363
|
+
comments=comment,
|
|
364
|
+
)
|
|
365
|
+
serializer = serializers.ApprovalWorkflowStageSerializer(stage, context={"request": request})
|
|
366
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
367
|
+
|
|
368
|
+
def restrict_queryset(self, request, *args, **kwargs):
|
|
369
|
+
"""
|
|
370
|
+
Apply special permissions as queryset filter on the /approve/, /deny/, and /comment/ endpoints.
|
|
371
|
+
|
|
372
|
+
Otherwise, same as ModelViewSetMixin.
|
|
373
|
+
"""
|
|
374
|
+
action_to_method = {"approve": "change", "deny": "change", "comment": "change"}
|
|
375
|
+
if request.user.is_authenticated and self.action in action_to_method:
|
|
376
|
+
self.queryset = self.queryset.restrict(request.user, action_to_method[self.action])
|
|
377
|
+
else:
|
|
378
|
+
super().restrict_queryset(request, *args, **kwargs)
|
|
379
|
+
|
|
380
|
+
class ApprovalWorkflowStageChangePermission(TokenPermissions):
|
|
381
|
+
"""
|
|
382
|
+
Enforce `change_approvalworkflowstage` permission (instead of default `add_approvalworkflowstage` for POST).
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
perms_map = {
|
|
386
|
+
"POST": ["%(app_label)s.change_approvalworkflowstage"],
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
@extend_schema(
|
|
390
|
+
methods=["post"],
|
|
391
|
+
request=None,
|
|
392
|
+
responses={"200": serializers.ApprovalWorkflowStageSerializer},
|
|
393
|
+
)
|
|
394
|
+
@action(
|
|
395
|
+
detail=True,
|
|
396
|
+
methods=["post"],
|
|
397
|
+
permission_classes=[ApprovalWorkflowStageChangePermission],
|
|
398
|
+
)
|
|
399
|
+
def approve(self, request, pk=None):
|
|
400
|
+
"""Approve the approval workflow stage."""
|
|
401
|
+
return self._handle_approve_deny_response(request, action_type="approve")
|
|
402
|
+
|
|
403
|
+
@extend_schema(
|
|
404
|
+
methods=["post"],
|
|
405
|
+
request=None,
|
|
406
|
+
responses={"200": serializers.ApprovalWorkflowStageSerializer},
|
|
407
|
+
)
|
|
408
|
+
@action(
|
|
409
|
+
detail=True,
|
|
410
|
+
methods=["post"],
|
|
411
|
+
permission_classes=[ApprovalWorkflowStageChangePermission],
|
|
412
|
+
)
|
|
413
|
+
def deny(self, request, pk=None):
|
|
414
|
+
"""Deny the approval workflow stage."""
|
|
415
|
+
return self._handle_approve_deny_response(request, action_type="deny")
|
|
416
|
+
|
|
417
|
+
@extend_schema(
|
|
418
|
+
methods=["post"],
|
|
419
|
+
request=None,
|
|
420
|
+
responses={"200": serializers.ApprovalWorkflowStageSerializer},
|
|
421
|
+
)
|
|
422
|
+
@action(detail=True, methods=["post"], permission_classes=[ApprovalWorkflowStageChangePermission])
|
|
423
|
+
def comment(self, request, pk=None):
|
|
424
|
+
"""Add a comment to the specific stage (without approving or denying)."""
|
|
425
|
+
stage = self.get_object()
|
|
426
|
+
|
|
427
|
+
comment = request.data.get("comments", "")
|
|
428
|
+
if not comment:
|
|
429
|
+
return Response(
|
|
430
|
+
{"detail": "Comment cannot be empty."},
|
|
431
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
ApprovalWorkflowStageResponse.objects.create(
|
|
435
|
+
approval_workflow_stage=stage, user=request.user, state=stage.state, comments=comment
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
serializer = serializers.ApprovalWorkflowStageSerializer(stage, context={"request": request})
|
|
439
|
+
return Response(serializer.data, status=status.HTTP_200_OK)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class ApprovalWorkflowStageResponseViewSet(ModelViewSet):
|
|
443
|
+
"""ApprovalWorkflowStageResponse viewset."""
|
|
444
|
+
|
|
445
|
+
queryset = ApprovalWorkflowStageResponse.objects.all()
|
|
446
|
+
serializer_class = serializers.ApprovalWorkflowStageResponseSerializer
|
|
447
|
+
filterset_class = filters.ApprovalWorkflowStageResponseFilterSet
|
|
448
|
+
|
|
449
|
+
|
|
259
450
|
#
|
|
260
451
|
# Contacts
|
|
261
452
|
#
|
|
@@ -472,8 +663,8 @@ class GraphQLQueryViewSet(NotesViewSetMixin, ModelViewSet):
|
|
|
472
663
|
def run(self, request, pk):
|
|
473
664
|
try:
|
|
474
665
|
query = get_object_or_404(self.queryset, pk=pk)
|
|
475
|
-
result = execute_saved_query(query.name, variables=request.data.get("variables"), request=request)
|
|
476
|
-
return Response(result)
|
|
666
|
+
result = execute_saved_query(query.name, variables=request.data.get("variables"), request=request)
|
|
667
|
+
return Response({"data": result.data, "errors": result.errors})
|
|
477
668
|
except GraphQLError as error:
|
|
478
669
|
return Response(
|
|
479
670
|
{"errors": [GraphQLView.format_error(error)]},
|
|
@@ -590,21 +781,6 @@ class JobViewSetBase(
|
|
|
590
781
|
raise PermissionDenied("This job is not enabled to be run.")
|
|
591
782
|
if not job_model.installed:
|
|
592
783
|
raise MethodNotAllowed(request.method, detail="This job is not presently installed and cannot be run")
|
|
593
|
-
if job_model.has_sensitive_variables:
|
|
594
|
-
if (
|
|
595
|
-
"schedule" in request.data
|
|
596
|
-
and "interval" in request.data["schedule"]
|
|
597
|
-
and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
|
|
598
|
-
):
|
|
599
|
-
raise ValidationError(
|
|
600
|
-
{"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
|
|
601
|
-
)
|
|
602
|
-
if job_model.approval_required:
|
|
603
|
-
raise ValidationError(
|
|
604
|
-
"Unable to run or schedule job: "
|
|
605
|
-
"This job is flagged as possibly having sensitive variables but is also flagged as requiring approval."
|
|
606
|
-
"One of these two flags must be removed before this job can be scheduled or run."
|
|
607
|
-
)
|
|
608
784
|
|
|
609
785
|
job_class = job_model.job_class
|
|
610
786
|
if job_class is None:
|
|
@@ -717,53 +893,59 @@ class JobViewSetBase(
|
|
|
717
893
|
# Default to a null JobResult.
|
|
718
894
|
job_result = None
|
|
719
895
|
|
|
720
|
-
#
|
|
721
|
-
if
|
|
722
|
-
|
|
723
|
-
approval_required = not dryrun and job_model.approval_required
|
|
724
|
-
else:
|
|
725
|
-
approval_required = job_model.approval_required
|
|
896
|
+
# Set schedule for jobs if request did not supply schedule data
|
|
897
|
+
if schedule_data is None:
|
|
898
|
+
schedule_data = {"interval": JobExecutionType.TYPE_IMMEDIATELY, "start_time": timezone.now()}
|
|
726
899
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
900
|
+
schedule = ScheduledJob.create_schedule(
|
|
901
|
+
job_model,
|
|
902
|
+
request.user,
|
|
903
|
+
name=schedule_data.get("name"),
|
|
904
|
+
start_time=schedule_data.get("start_time"),
|
|
905
|
+
interval=schedule_data.get("interval"),
|
|
906
|
+
crontab=schedule_data.get("crontab", ""),
|
|
907
|
+
job_queue=job_queue,
|
|
908
|
+
validated_save=False,
|
|
909
|
+
**job_class.serialize_data(cleaned_data),
|
|
910
|
+
)
|
|
730
911
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
912
|
+
scheduled_job_has_approval_workflow = schedule.has_approval_workflow_definition()
|
|
913
|
+
if job_model.has_sensitive_variables:
|
|
914
|
+
if (
|
|
915
|
+
"schedule" in request.data
|
|
916
|
+
and "interval" in request.data["schedule"]
|
|
917
|
+
and request.data["schedule"]["interval"] != JobExecutionType.TYPE_IMMEDIATELY
|
|
918
|
+
):
|
|
919
|
+
raise ValidationError(
|
|
920
|
+
{"schedule": {"interval": ["Unable to schedule job: Job may have sensitive input variables"]}}
|
|
921
|
+
)
|
|
922
|
+
# check approval_required pointer
|
|
923
|
+
if scheduled_job_has_approval_workflow:
|
|
924
|
+
raise ValidationError(
|
|
925
|
+
"Unable to run or schedule job: "
|
|
926
|
+
"This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
|
|
927
|
+
"Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False."
|
|
928
|
+
)
|
|
734
929
|
|
|
735
|
-
#
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
job_queue=job_queue,
|
|
746
|
-
**job_class.serialize_data(cleaned_data),
|
|
747
|
-
)
|
|
930
|
+
# Approval is not required for dryrun
|
|
931
|
+
# TODO: remove this once we have the ability to configure an approval workflow to ignore jobs with specific parameters(including `dryrun`)
|
|
932
|
+
dryrun = data.get("dryrun", False) if job_class.supports_dryrun else False
|
|
933
|
+
|
|
934
|
+
if (not dryrun and scheduled_job_has_approval_workflow) or schedule_data[
|
|
935
|
+
"interval"
|
|
936
|
+
] in JobExecutionType.SCHEDULE_CHOICES:
|
|
937
|
+
schedule.validated_save()
|
|
938
|
+
serializer = serializers.ScheduledJobSerializer(schedule, context={"request": request})
|
|
939
|
+
return Response({"scheduled_job": serializer.data, "job_result": None}, status=status.HTTP_201_CREATED)
|
|
748
940
|
else:
|
|
749
|
-
schedule = None
|
|
750
|
-
|
|
751
|
-
# ... If we can't create one, create a JobResult instead.
|
|
752
|
-
if schedule is None:
|
|
753
941
|
job_result = JobResult.enqueue_job(
|
|
754
942
|
job_model,
|
|
755
943
|
request.user,
|
|
756
944
|
job_queue=job_queue,
|
|
757
945
|
**job_class.serialize_data(cleaned_data),
|
|
758
946
|
)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
data = {"scheduled_job": None, "job_result": None}
|
|
762
|
-
if schedule:
|
|
763
|
-
data["scheduled_job"] = serializers.ScheduledJobSerializer(schedule, context={"request": request}).data
|
|
764
|
-
if job_result:
|
|
765
|
-
data["job_result"] = serializers.JobResultSerializer(job_result, context={"request": request}).data
|
|
766
|
-
return Response(data, status=status.HTTP_201_CREATED)
|
|
947
|
+
serializer = serializers.JobResultSerializer(job_result, context={"request": request})
|
|
948
|
+
return Response({"scheduled_job": None, "job_result": serializer.data}, status=status.HTTP_201_CREATED)
|
|
767
949
|
|
|
768
950
|
|
|
769
951
|
class JobViewSet(
|
|
@@ -930,95 +1112,16 @@ class ScheduledJobViewSet(
|
|
|
930
1112
|
|
|
931
1113
|
def restrict_queryset(self, request, *args, **kwargs):
|
|
932
1114
|
"""
|
|
933
|
-
Apply special permissions as queryset filter on the /
|
|
1115
|
+
Apply special permissions as queryset filter on the /dry-run/ endpoints.
|
|
934
1116
|
|
|
935
1117
|
Otherwise, same as ModelViewSetMixin.
|
|
936
1118
|
"""
|
|
937
|
-
action_to_method = {"
|
|
1119
|
+
action_to_method = {"dry-run": "view"}
|
|
938
1120
|
if request.user.is_authenticated and self.action in action_to_method:
|
|
939
1121
|
self.queryset = self.queryset.restrict(request.user, action_to_method[self.action])
|
|
940
1122
|
else:
|
|
941
1123
|
super().restrict_queryset(request, *args, **kwargs)
|
|
942
1124
|
|
|
943
|
-
class ScheduledJobChangePermissions(TokenPermissions):
|
|
944
|
-
"""
|
|
945
|
-
As nautobot.core.api.authentication.TokenPermissions, but enforcing change_scheduledjob not add_scheduledjob.
|
|
946
|
-
"""
|
|
947
|
-
|
|
948
|
-
perms_map = {
|
|
949
|
-
"POST": ["%(app_label)s.change_%(model_name)s"],
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
@extend_schema(
|
|
953
|
-
methods=["post"],
|
|
954
|
-
responses={"200": serializers.ScheduledJobSerializer},
|
|
955
|
-
request=None,
|
|
956
|
-
parameters=[
|
|
957
|
-
OpenApiParameter(
|
|
958
|
-
"force",
|
|
959
|
-
location=OpenApiParameter.QUERY,
|
|
960
|
-
description="force execution even if start time has passed",
|
|
961
|
-
type=OpenApiTypes.BOOL,
|
|
962
|
-
)
|
|
963
|
-
],
|
|
964
|
-
)
|
|
965
|
-
@action(detail=True, methods=["post"], permission_classes=[ScheduledJobChangePermissions])
|
|
966
|
-
def approve(self, request, pk):
|
|
967
|
-
scheduled_job = get_object_or_404(self.queryset, pk=pk)
|
|
968
|
-
|
|
969
|
-
if not Job.objects.check_perms(request.user, instance=scheduled_job.job_model, action="approve"):
|
|
970
|
-
raise PermissionDenied("You do not have permission to approve this request.")
|
|
971
|
-
|
|
972
|
-
# Mark the scheduled_job as approved, allowing the schedular to schedule the job execution task
|
|
973
|
-
if request.user == scheduled_job.user:
|
|
974
|
-
# The requestor *cannot* approve their own job
|
|
975
|
-
return Response("You cannot approve your own job request!", status=403)
|
|
976
|
-
|
|
977
|
-
if (
|
|
978
|
-
scheduled_job.one_off
|
|
979
|
-
and scheduled_job.start_time < timezone.now()
|
|
980
|
-
and not request.query_params.get("force")
|
|
981
|
-
):
|
|
982
|
-
return Response(
|
|
983
|
-
"The job's start time is in the past. If you want to force a run anyway, add the `force` query parameter.",
|
|
984
|
-
status=400,
|
|
985
|
-
)
|
|
986
|
-
|
|
987
|
-
scheduled_job.approved_by_user = request.user
|
|
988
|
-
scheduled_job.approved_at = timezone.now()
|
|
989
|
-
scheduled_job.save()
|
|
990
|
-
publish_event_payload = {"data": serialize_object_v2(scheduled_job)}
|
|
991
|
-
publish_event(topic="nautobot.jobs.approval.approved", payload=publish_event_payload)
|
|
992
|
-
serializer = serializers.ScheduledJobSerializer(scheduled_job, context={"request": request})
|
|
993
|
-
|
|
994
|
-
return Response(serializer.data)
|
|
995
|
-
|
|
996
|
-
class ScheduledJobDeletePermissions(TokenPermissions):
|
|
997
|
-
"""
|
|
998
|
-
As nautobot.core.api.authentication.TokenPermissions, but enforcing delete_scheduledjob not add_scheduledjob.
|
|
999
|
-
"""
|
|
1000
|
-
|
|
1001
|
-
perms_map = {
|
|
1002
|
-
"POST": ["%(app_label)s.delete_%(model_name)s"],
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
@extend_schema(
|
|
1006
|
-
methods=["post"],
|
|
1007
|
-
request=None,
|
|
1008
|
-
)
|
|
1009
|
-
@action(detail=True, methods=["post"], permission_classes=[ScheduledJobDeletePermissions])
|
|
1010
|
-
def deny(self, request, pk):
|
|
1011
|
-
scheduled_job = get_object_or_404(ScheduledJob, pk=pk)
|
|
1012
|
-
|
|
1013
|
-
if not Job.objects.check_perms(request.user, instance=scheduled_job.job_model, action="approve"):
|
|
1014
|
-
raise PermissionDenied("You do not have permission to deny this request.")
|
|
1015
|
-
|
|
1016
|
-
publish_event_payload = {"data": serialize_object_v2(scheduled_job)}
|
|
1017
|
-
publish_event(topic="nautobot.jobs.approval.denied", payload=publish_event_payload)
|
|
1018
|
-
scheduled_job.delete()
|
|
1019
|
-
|
|
1020
|
-
return Response(None)
|
|
1021
|
-
|
|
1022
1125
|
class ScheduledJobViewPermissions(TokenPermissions):
|
|
1023
1126
|
"""
|
|
1024
1127
|
As nautobot.core.api.authentication.TokenPermissions, but enforcing view_scheduledjob not add_scheduledjob.
|
|
@@ -1043,7 +1146,7 @@ class ScheduledJobViewSet(
|
|
|
1043
1146
|
def dry_run(self, request, pk):
|
|
1044
1147
|
scheduled_job = get_object_or_404(ScheduledJob, pk=pk)
|
|
1045
1148
|
job_model = scheduled_job.job_model
|
|
1046
|
-
if job_model is None or not
|
|
1149
|
+
if job_model is None or not scheduled_job.runnable:
|
|
1047
1150
|
raise MethodNotAllowed("This job cannot be dry-run at this time.")
|
|
1048
1151
|
if not job_model.supports_dryrun:
|
|
1049
1152
|
raise MethodNotAllowed("This job does not support dry-run.")
|
nautobot/extras/choices.py
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
from celery import states
|
|
2
2
|
|
|
3
3
|
from nautobot.core.choices import ChoiceSet
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Approval Workflows
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ApprovalWorkflowStateChoices(ChoiceSet):
|
|
11
|
+
"""
|
|
12
|
+
Choices for:
|
|
13
|
+
1. current_state field on the ApprovalWorkflow model.
|
|
14
|
+
2. state field on the ApprovalWorkflowStage model.
|
|
15
|
+
3. state field on the ApprovalWorkflowStageResponse model.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
PENDING = "Pending"
|
|
19
|
+
APPROVED = "Approved"
|
|
20
|
+
DENIED = "Denied"
|
|
21
|
+
CANCELED = "Canceled"
|
|
22
|
+
|
|
23
|
+
CHOICES = (
|
|
24
|
+
(PENDING, "Pending"),
|
|
25
|
+
(APPROVED, "Approved"),
|
|
26
|
+
(DENIED, "Denied"),
|
|
27
|
+
(CANCELED, "Canceled"),
|
|
28
|
+
)
|
|
29
|
+
CSS_CLASSES = {
|
|
30
|
+
PENDING: "info",
|
|
31
|
+
APPROVED: "success",
|
|
32
|
+
DENIED: "danger",
|
|
33
|
+
CANCELED: "warning",
|
|
34
|
+
}
|
|
35
|
+
|
|
5
36
|
|
|
6
37
|
#
|
|
7
38
|
# Banners (currently plugin-specific)
|
|
@@ -127,7 +158,7 @@ class CustomFieldTypeChoices(ChoiceSet):
|
|
|
127
158
|
|
|
128
159
|
|
|
129
160
|
class ButtonClassChoices(ChoiceSet):
|
|
130
|
-
CLASS_DEFAULT = "default"
|
|
161
|
+
CLASS_DEFAULT = "default" # maps to "secondary" in v3 UI, but kept for backwards compatibility
|
|
131
162
|
CLASS_PRIMARY = "primary"
|
|
132
163
|
CLASS_SUCCESS = "success"
|
|
133
164
|
CLASS_INFO = "info"
|
|
@@ -139,23 +170,13 @@ class ButtonClassChoices(ChoiceSet):
|
|
|
139
170
|
(CLASS_DEFAULT, "Default"),
|
|
140
171
|
(CLASS_PRIMARY, "Primary (blue)"),
|
|
141
172
|
(CLASS_SUCCESS, "Success (green)"),
|
|
142
|
-
(CLASS_INFO, "Info (
|
|
173
|
+
(CLASS_INFO, "Info (blue)"),
|
|
143
174
|
(CLASS_WARNING, "Warning (orange)"),
|
|
144
175
|
(CLASS_DANGER, "Danger (red)"),
|
|
145
176
|
(CLASS_LINK, "None (link)"),
|
|
146
177
|
)
|
|
147
178
|
|
|
148
179
|
|
|
149
|
-
#
|
|
150
|
-
# CustomLinks
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@class_deprecated_in_favor_of(ButtonClassChoices)
|
|
155
|
-
class CustomLinkButtonClassChoices(ButtonClassChoices):
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
|
|
159
180
|
#
|
|
160
181
|
# Dynamic Groups
|
|
161
182
|
#
|
nautobot/extras/constants.py
CHANGED
|
@@ -27,7 +27,6 @@ JOB_OVERRIDABLE_FIELDS = (
|
|
|
27
27
|
"description",
|
|
28
28
|
"dryrun_default",
|
|
29
29
|
"hidden",
|
|
30
|
-
"approval_required",
|
|
31
30
|
"soft_time_limit",
|
|
32
31
|
"time_limit",
|
|
33
32
|
"has_sensitive_variables",
|
|
@@ -51,6 +50,7 @@ CHANGELOG_MAX_OBJECT_REPR = 200
|
|
|
51
50
|
# JobResult custom Celery kwargs
|
|
52
51
|
JOB_RESULT_CUSTOM_CELERY_KWARGS = (
|
|
53
52
|
"nautobot_job_profile",
|
|
53
|
+
"nautobot_job_branch_name",
|
|
54
54
|
"nautobot_job_job_model_id",
|
|
55
55
|
"nautobot_job_scheduled_job_id",
|
|
56
56
|
"nautobot_job_user_id",
|
|
@@ -21,16 +21,18 @@ class ChangeContext:
|
|
|
21
21
|
one will be generated to relate any changes to this transaction. Convenience
|
|
22
22
|
classes are provided for each context.
|
|
23
23
|
|
|
24
|
-
:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
Args:
|
|
25
|
+
user (User): User object
|
|
26
|
+
request (WSGIRequest): object to retrieve user from django rest framework after authentication is performed
|
|
27
|
+
context (ObjectChangeEventContextChoices): Context of the transaction
|
|
28
|
+
context_detail (Optional[str]): extra details about the transaction (ex: plugin name that initiated the change)
|
|
29
|
+
change_id (Optional[UUID]): Object to uniquely identify the transaction. One will be generated if not supplied
|
|
29
30
|
|
|
30
31
|
The next two parameters are used as caching fields when updating an object with no previous ObjectChange instances.
|
|
31
32
|
They are used to populate the `pre_change` field of an ObjectChange snapshot in get_snapshot().
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
|
|
34
|
+
pre_object_data (dict): Optional dictionary of serialized object data to be used in the object snapshot
|
|
35
|
+
pre_object_data_v2 (dict): Optional dictionary of serialized object data to be used in the object snapshot
|
|
34
36
|
"""
|
|
35
37
|
|
|
36
38
|
defer_object_changes = False # advanced usage, for creating object changes in bulk
|
|
@@ -151,12 +153,10 @@ class WebChangeContext(ChangeContext):
|
|
|
151
153
|
|
|
152
154
|
|
|
153
155
|
@contextmanager
|
|
154
|
-
def change_logging(change_context):
|
|
156
|
+
def change_logging(change_context: ChangeContext):
|
|
155
157
|
"""
|
|
156
158
|
Enable change logging by connecting the appropriate signals to their receivers before code is run, and
|
|
157
159
|
disconnecting them afterward.
|
|
158
|
-
|
|
159
|
-
:param change_context: ChangeContext instance
|
|
160
160
|
"""
|
|
161
161
|
|
|
162
162
|
# Set change logging state
|
|
@@ -182,22 +182,22 @@ def web_request_context(
|
|
|
182
182
|
By default, when working with the Django ORM, neither change logging nor webhook processing occur
|
|
183
183
|
unless manually invoked and this context manager handles those functions. A valid User object must be provided.
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
185
|
+
Examples:
|
|
186
|
+
>>> from nautobot.extras.context_managers import web_request_context
|
|
187
|
+
>>> user = User.objects.get(username="admin")
|
|
188
|
+
>>> with web_request_context(user, context_detail="manual-fix"):
|
|
189
|
+
... lt = Location.objects.get(name="Root")
|
|
190
|
+
... lax = Location(name="LAX", location_type=lt)
|
|
191
|
+
... lax.validated_save()
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
user (User): User object
|
|
195
|
+
context_detail (str): Optional extra details about the transaction (ex: plugin name that initiated the change)
|
|
196
|
+
change_id (Optional[UUID]): Object to uniquely identify the transaction. One will be generated if not supplied
|
|
197
|
+
context (str): Optional string value of the generated change log entries' "change_context" field.
|
|
198
|
+
Defaults to `ObjectChangeEventContextChoices.CONTEXT_ORM`.
|
|
199
|
+
Valid choices are in `nautobot.extras.choices.ObjectChangeEventContextChoices`.
|
|
200
|
+
request (Request): Optional web request instance, one will be generated if not supplied
|
|
201
201
|
"""
|
|
202
202
|
from nautobot.extras.jobs import enqueue_job_hooks # prevent circular import
|
|
203
203
|
|
|
@@ -1058,6 +1058,19 @@ def delete_git_graphql_queries(repository_record, job_result, preserve=None):
|
|
|
1058
1058
|
job_result.log(error_msg, level_choice=LogLevelChoices.LOG_ERROR, grouping="graphql queries")
|
|
1059
1059
|
|
|
1060
1060
|
|
|
1061
|
+
def refresh_git_data_compliance_rules(repository_record, job_result, delete=False): # pylint: disable=W0613
|
|
1062
|
+
"""Callback function for GitRepository updates - refresh all DataComplianceRules managed by this repository."""
|
|
1063
|
+
from nautobot.data_validation.custom_validators import get_data_compliance_classes_from_git_repo
|
|
1064
|
+
|
|
1065
|
+
if "data_validation.data_compliance_rule" in repository_record.provided_contents:
|
|
1066
|
+
for compliance_class in get_data_compliance_classes_from_git_repo(repository_record):
|
|
1067
|
+
job_result.log(
|
|
1068
|
+
f"Found class {compliance_class.__name__!s}",
|
|
1069
|
+
level_choice=LogLevelChoices.LOG_INFO,
|
|
1070
|
+
grouping="data compliance rules",
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
|
|
1061
1074
|
# Register built-in callbacks for data types potentially provided by a GitRepository
|
|
1062
1075
|
register_datasource_contents(
|
|
1063
1076
|
[
|
|
@@ -1111,5 +1124,14 @@ register_datasource_contents(
|
|
|
1111
1124
|
callback=refresh_git_graphql_queries,
|
|
1112
1125
|
),
|
|
1113
1126
|
),
|
|
1127
|
+
(
|
|
1128
|
+
"extras.gitrepository",
|
|
1129
|
+
DatasourceContent(
|
|
1130
|
+
name="data compliance rules",
|
|
1131
|
+
content_identifier="data_validation.data_compliance_rule",
|
|
1132
|
+
icon="mdi-file-document-outline",
|
|
1133
|
+
callback=refresh_git_data_compliance_rules,
|
|
1134
|
+
),
|
|
1135
|
+
),
|
|
1114
1136
|
]
|
|
1115
1137
|
)
|