nautobot 3.0.0a3__py3-none-any.whl → 3.0.0rc1__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/choices.py +4 -0
- nautobot/apps/ui.py +4 -0
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/core/api/serializers.py +1 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +36 -13
- nautobot/core/cli/migrate_deprecated_templates.py +36 -9
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +56 -0
- nautobot/core/management/commands/generate_test_data.py +3 -3
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +17 -7
- nautobot/core/settings.yaml +4 -26
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/tab/content_wrapper.html +4 -4
- nautobot/core/templates/echarts/echarts.html +21 -8
- nautobot/core/templates/generic/object_bulk_create.html +2 -2
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/generic/object_retrieve.html +2 -2
- nautobot/core/templates/graphene/graphiql.html +0 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/header.html +10 -0
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +1 -8
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +0 -6
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +33 -0
- nautobot/core/templates/utilities/theme_preview.html +3 -0
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templatetags/helpers.py +24 -12
- nautobot/core/testing/integration.py +24 -13
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -17
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/runner.py +1 -2
- nautobot/core/tests/test_breadcrumbs.py +21 -21
- nautobot/core/tests/test_jobs.py +73 -6
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_templatetags_helpers.py +9 -0
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_ui.py +122 -3
- nautobot/core/tests/test_utils.py +41 -1
- nautobot/core/ui/breadcrumbs.py +68 -17
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +49 -65
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +54 -46
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +8 -8
- nautobot/core/utils/filtering.py +11 -1
- nautobot/core/utils/lookup.py +46 -0
- nautobot/core/views/mixins.py +31 -20
- nautobot/core/views/renderers.py +2 -3
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -2
- nautobot/dcim/api/serializers.py +3 -0
- nautobot/dcim/choices.py +49 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filters.py +13 -1
- nautobot/dcim/forms.py +89 -3
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/{0078_remove_device_location_tenant_name_uniqueness.py → 0079_remove_device_location_tenant_name_uniqueness.py} +1 -1
- nautobot/dcim/migrations/{0079_device_name_data_migration.py → 0080_device_name_data_migration.py} +1 -1
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +22 -1
- nautobot/dcim/models/devices.py +17 -4
- nautobot/dcim/tables/devices.py +15 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +1 -1
- nautobot/dcim/templates/dcim/cable_trace.html +0 -2
- nautobot/dcim/templates/dcim/consoleport.html +1 -1
- nautobot/dcim/templates/dcim/consoleserverport.html +1 -1
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/frontport.html +1 -1
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +9 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +1 -1
- nautobot/dcim/templates/dcim/rack_elevation_list.html +1 -1
- nautobot/dcim/templates/dcim/rack_retrieve.html +0 -11
- nautobot/dcim/templates/dcim/rearport.html +1 -1
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_update.html +1 -1
- nautobot/dcim/tests/integration/test_controller.py +3 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +186 -6
- nautobot/dcim/tests/test_filters.py +43 -1
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +265 -0
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +69 -7
- nautobot/dcim/views.py +232 -126
- nautobot/extras/api/views.py +51 -44
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/filters.py +19 -2
- nautobot/extras/forms/forms.py +9 -2
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +2 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/approvals.py +11 -1
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/jobs.py +1 -3
- nautobot/extras/models/models.py +10 -2
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/views.py +0 -5
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/tables.py +12 -0
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/objectchange_retrieve.html +0 -2
- nautobot/extras/templates/extras/plugin_detail.html +3 -3
- nautobot/extras/templates/extras/secret_create.html +1 -1
- nautobot/extras/tests/integration/test_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_customfields.py +1 -3
- nautobot/extras/tests/integration/test_dynamicgroups.py +7 -8
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +63 -0
- nautobot/extras/tests/test_changelog.py +24 -2
- nautobot/extras/tests/test_filters.py +36 -3
- nautobot/extras/tests/test_models.py +38 -2
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +22 -83
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +83 -52
- nautobot/ipam/filters.py +26 -0
- nautobot/ipam/tables.py +6 -0
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/tests/test_filters.py +26 -1
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/ipam/views.py +9 -7
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/nautobot.js +0 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/nautobot.scss +51 -2
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +1 -0
- nautobot/virtualization/views.py +4 -1
- nautobot/vpn/factory.py +25 -15
- nautobot/vpn/filters.py +1 -0
- nautobot/vpn/forms.py +1 -0
- nautobot/vpn/migrations/0001_initial.py +1 -1
- nautobot/vpn/models.py +16 -8
- nautobot/vpn/tables.py +5 -2
- nautobot/vpn/tests/test_api.py +0 -5
- nautobot/vpn/tests/test_forms.py +1 -2
- nautobot/vpn/tests/test_models.py +57 -7
- nautobot/vpn/tests/test_views.py +22 -3
- nautobot/vpn/views.py +78 -20
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +4 -4
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +243 -352
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/cable.html +0 -2
- nautobot/dcim/templates/dcim/cable_edit.html +0 -2
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -2
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/vpn/templates/vpn/vpnprofile.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +0 -0
|
@@ -29,6 +29,7 @@ from nautobot.core.utils.permissions import get_permission_for_model
|
|
|
29
29
|
from nautobot.dcim.models import (
|
|
30
30
|
ConsolePort,
|
|
31
31
|
Device,
|
|
32
|
+
DeviceFamily,
|
|
32
33
|
DeviceType,
|
|
33
34
|
Interface,
|
|
34
35
|
Location,
|
|
@@ -96,7 +97,6 @@ from nautobot.extras.models import (
|
|
|
96
97
|
from nautobot.extras.templatetags.job_buttons import NO_CONFIRM_BUTTON
|
|
97
98
|
from nautobot.extras.tests.constants import BIG_GRAPHQL_DEVICE_QUERY
|
|
98
99
|
from nautobot.extras.tests.test_jobs import get_job_class_and_model
|
|
99
|
-
from nautobot.extras.tests.test_relationships import RequiredRelationshipTestMixin
|
|
100
100
|
from nautobot.extras.utils import get_pending_approval_workflow_stages, RoleModelsQuery, TaggableClassesQuery
|
|
101
101
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN, VLANGroup, VRF
|
|
102
102
|
from nautobot.tenancy.models import Tenant
|
|
@@ -730,6 +730,7 @@ class ConfigContextTestCase(
|
|
|
730
730
|
"regions": [],
|
|
731
731
|
"locations": [location.pk],
|
|
732
732
|
"roles": [],
|
|
733
|
+
"device_families": [DeviceFamily.objects.first().pk],
|
|
733
734
|
"device_types": [],
|
|
734
735
|
"platforms": [],
|
|
735
736
|
"tenant_groups": [],
|
|
@@ -764,6 +765,7 @@ class ConfigContextTestCase(
|
|
|
764
765
|
"regions": [],
|
|
765
766
|
"locations": [],
|
|
766
767
|
"roles": [],
|
|
768
|
+
"device_families": [],
|
|
767
769
|
"device_types": [],
|
|
768
770
|
"platforms": [],
|
|
769
771
|
"tenant_groups": [],
|
|
@@ -803,6 +805,7 @@ class ConfigContextTestCase(
|
|
|
803
805
|
"regions": [],
|
|
804
806
|
"locations": [],
|
|
805
807
|
"roles": [],
|
|
808
|
+
"device_families": [],
|
|
806
809
|
"device_types": [],
|
|
807
810
|
"platforms": [],
|
|
808
811
|
"tenant_groups": [],
|
|
@@ -2755,59 +2758,6 @@ class ScheduledJobTestCase(
|
|
|
2755
2758
|
crontab="15 10 * * *",
|
|
2756
2759
|
)
|
|
2757
2760
|
|
|
2758
|
-
def test_only_enabled_is_listed(self):
|
|
2759
|
-
self.add_permissions("extras.view_scheduledjob")
|
|
2760
|
-
|
|
2761
|
-
# this should not appear, since it's not enabled
|
|
2762
|
-
ScheduledJob.objects.create(
|
|
2763
|
-
enabled=False,
|
|
2764
|
-
name="test4",
|
|
2765
|
-
task="pass_job.TestPassJob",
|
|
2766
|
-
interval=JobExecutionType.TYPE_IMMEDIATELY,
|
|
2767
|
-
user=self.user,
|
|
2768
|
-
start_time=timezone.now(),
|
|
2769
|
-
)
|
|
2770
|
-
|
|
2771
|
-
response = self.client.get(self._get_url("list"))
|
|
2772
|
-
self.assertHttpStatus(response, 200)
|
|
2773
|
-
self.assertNotIn("test4", extract_page_body(response.content.decode(response.charset)))
|
|
2774
|
-
|
|
2775
|
-
def test_approved_required_jobs_are_listed_only_when_approved(self):
|
|
2776
|
-
self.add_permissions("extras.view_scheduledjob")
|
|
2777
|
-
|
|
2778
|
-
# this should not appear, since it's not approved
|
|
2779
|
-
ScheduledJob.objects.create(
|
|
2780
|
-
enabled=True,
|
|
2781
|
-
approval_required=True,
|
|
2782
|
-
decision_date=None,
|
|
2783
|
-
name="test4",
|
|
2784
|
-
task="pass_job.TestPassJob",
|
|
2785
|
-
interval=JobExecutionType.TYPE_IMMEDIATELY,
|
|
2786
|
-
user=self.user,
|
|
2787
|
-
start_time=timezone.now(),
|
|
2788
|
-
)
|
|
2789
|
-
ScheduledJob.objects.create(
|
|
2790
|
-
enabled=True,
|
|
2791
|
-
approval_required=False,
|
|
2792
|
-
name="test5",
|
|
2793
|
-
task="pass_job.TestPassJob",
|
|
2794
|
-
interval=JobExecutionType.TYPE_IMMEDIATELY,
|
|
2795
|
-
user=self.user,
|
|
2796
|
-
start_time=timezone.now(),
|
|
2797
|
-
)
|
|
2798
|
-
response = self.client.get(self._get_url("list"))
|
|
2799
|
-
self.assertHttpStatus(response, 200)
|
|
2800
|
-
self.assertNotIn("test4", extract_page_body(response.content.decode(response.charset)))
|
|
2801
|
-
self.assertIn("test5", extract_page_body(response.content.decode(response.charset)))
|
|
2802
|
-
|
|
2803
|
-
scheduled_job = ScheduledJob.objects.get(name="test4")
|
|
2804
|
-
scheduled_job.decision_date = timezone.now()
|
|
2805
|
-
scheduled_job.save()
|
|
2806
|
-
|
|
2807
|
-
response = self.client.get(self._get_url("list"))
|
|
2808
|
-
self.assertHttpStatus(response, 200)
|
|
2809
|
-
self.assertIn("test4", extract_page_body(response.content.decode(response.charset)))
|
|
2810
|
-
|
|
2811
2761
|
def test_non_valid_crontab_syntax(self):
|
|
2812
2762
|
self.add_permissions("extras.view_scheduledjob")
|
|
2813
2763
|
|
|
@@ -3291,7 +3241,6 @@ class JobTestCase(
|
|
|
3291
3241
|
|
|
3292
3242
|
result = JobResult.objects.latest()
|
|
3293
3243
|
self.assertRedirects(response, reverse("extras:jobresult", kwargs={"pk": result.pk}))
|
|
3294
|
-
mock_begin_approval_workflow.assert_not_called()
|
|
3295
3244
|
|
|
3296
3245
|
def test_rerun_job(self):
|
|
3297
3246
|
self.add_permissions("extras.run_job")
|
|
@@ -3328,7 +3277,8 @@ class JobTestCase(
|
|
|
3328
3277
|
)
|
|
3329
3278
|
self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
|
|
3330
3279
|
self.assertInHTML(
|
|
3331
|
-
'<input type="checkbox" name="_ignore_singleton_lock" id="id__ignore_singleton_lock" checked>',
|
|
3280
|
+
'<input type="checkbox" name="_ignore_singleton_lock" id="id__ignore_singleton_lock" class="form-check-input" checked>',
|
|
3281
|
+
content,
|
|
3332
3282
|
)
|
|
3333
3283
|
|
|
3334
3284
|
@mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
|
|
@@ -3511,11 +3461,13 @@ class JobTestCase(
|
|
|
3511
3461
|
self.add_permissions("extras.run_job")
|
|
3512
3462
|
self.add_permissions("extras.view_scheduledjob")
|
|
3513
3463
|
|
|
3514
|
-
ApprovalWorkflowDefinition
|
|
3464
|
+
workflow = ApprovalWorkflowDefinition(
|
|
3515
3465
|
name="Approval Definition",
|
|
3516
3466
|
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
3517
3467
|
weight=0,
|
|
3468
|
+
model_constraints={"job_model__name": self.test_pass.name},
|
|
3518
3469
|
)
|
|
3470
|
+
workflow.validated_save()
|
|
3519
3471
|
data = {
|
|
3520
3472
|
"_schedule_type": "future",
|
|
3521
3473
|
"_schedule_name": "test",
|
|
@@ -3523,14 +3475,15 @@ class JobTestCase(
|
|
|
3523
3475
|
}
|
|
3524
3476
|
|
|
3525
3477
|
for i, run_url in enumerate(self.run_urls):
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3478
|
+
with self.subTest(run_url=run_url):
|
|
3479
|
+
if "_schedule_name" in data:
|
|
3480
|
+
data["_schedule_name"] = f"test {i}"
|
|
3481
|
+
response = self.client.post(run_url, data)
|
|
3482
|
+
scheduled_job = ScheduledJob.objects.last()
|
|
3483
|
+
self.assertRedirects(
|
|
3484
|
+
response,
|
|
3485
|
+
reverse("extras:scheduledjob_approvalworkflow", args=[scheduled_job.pk]),
|
|
3486
|
+
)
|
|
3534
3487
|
|
|
3535
3488
|
@mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
|
|
3536
3489
|
def test_run_scheduled_job_with_no_approval_workflow_defined(self, _):
|
|
@@ -4068,17 +4021,7 @@ class ObjectMetadataTestCase(
|
|
|
4068
4021
|
self.assertNotIn(instance2.assigned_object.get_absolute_url(), content, msg=content)
|
|
4069
4022
|
|
|
4070
4023
|
|
|
4071
|
-
class RelationshipTestCase(
|
|
4072
|
-
ViewTestCases.CreateObjectViewTestCase,
|
|
4073
|
-
ViewTestCases.DeleteObjectViewTestCase,
|
|
4074
|
-
ViewTestCases.EditObjectViewTestCase,
|
|
4075
|
-
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
4076
|
-
ViewTestCases.GetObjectViewTestCase,
|
|
4077
|
-
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
4078
|
-
ViewTestCases.ListObjectsViewTestCase,
|
|
4079
|
-
RequiredRelationshipTestMixin,
|
|
4080
|
-
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
4081
|
-
):
|
|
4024
|
+
class RelationshipTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
4082
4025
|
model = Relationship
|
|
4083
4026
|
slug_source = "label"
|
|
4084
4027
|
slugify_function = staticmethod(slugify_dashes_to_underscores)
|
|
@@ -4592,7 +4535,6 @@ class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases
|
|
|
4592
4535
|
"remove_content_types": [device_ct.pk],
|
|
4593
4536
|
}
|
|
4594
4537
|
|
|
4595
|
-
@tag("fix_in_v3")
|
|
4596
4538
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
4597
4539
|
def test_view_with_content_types(self):
|
|
4598
4540
|
"""
|
|
@@ -4611,13 +4553,10 @@ class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase, ViewTestCases
|
|
|
4611
4553
|
if content_type not in role_content_types:
|
|
4612
4554
|
if result == "Contact Associations":
|
|
4613
4555
|
# AssociationContact Table in the contact tab should be there.
|
|
4614
|
-
self.assertInHTML(
|
|
4615
|
-
|
|
4616
|
-
response_body,
|
|
4617
|
-
)
|
|
4618
|
-
# ContactAssociationTable related to this role instances should not be there.
|
|
4556
|
+
self.assertInHTML(f"<strong>{result}</strong>", response_body)
|
|
4557
|
+
# ContactAssociationTable related to this role instances in the main tab should not be there.
|
|
4619
4558
|
self.assertNotIn(
|
|
4620
|
-
f'<strong>{result}</strong>\n </div>\n \n\n<table class="table table-hover nb-table-headings"
|
|
4559
|
+
f'<strong>{result}</strong>\n </div>\n \n\n\n\n <table class="table table-hover nb-table-headings">',
|
|
4621
4560
|
response_body,
|
|
4622
4561
|
)
|
|
4623
4562
|
else:
|
nautobot/extras/urls.py
CHANGED
|
@@ -5,7 +5,6 @@ from nautobot.core.views.routers import NautobotUIViewSetRouter
|
|
|
5
5
|
from nautobot.extras import views
|
|
6
6
|
from nautobot.extras.models import (
|
|
7
7
|
Job,
|
|
8
|
-
Relationship,
|
|
9
8
|
ScheduledJob,
|
|
10
9
|
)
|
|
11
10
|
|
|
@@ -135,19 +134,6 @@ urlpatterns = [
|
|
|
135
134
|
name="scheduledjob_approvalworkflow",
|
|
136
135
|
kwargs={"model": ScheduledJob},
|
|
137
136
|
),
|
|
138
|
-
# Custom relationships
|
|
139
|
-
path(
|
|
140
|
-
"relationships/<uuid:pk>/changelog/",
|
|
141
|
-
views.ObjectChangeLogView.as_view(),
|
|
142
|
-
name="relationship_changelog",
|
|
143
|
-
kwargs={"model": Relationship},
|
|
144
|
-
),
|
|
145
|
-
path(
|
|
146
|
-
"relationships/<uuid:pk>/notes/",
|
|
147
|
-
views.ObjectNotesView.as_view(),
|
|
148
|
-
name="relationship_notes",
|
|
149
|
-
kwargs={"model": Relationship},
|
|
150
|
-
),
|
|
151
137
|
# Secrets
|
|
152
138
|
path(
|
|
153
139
|
"secrets/provider/<str:provider_slug>/form/",
|
nautobot/extras/views.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from functools import partial
|
|
1
2
|
import logging
|
|
2
3
|
from typing import Optional
|
|
3
4
|
from urllib.parse import parse_qs
|
|
@@ -60,6 +61,7 @@ from nautobot.core.views.mixins import (
|
|
|
60
61
|
ObjectBulkDestroyViewMixin,
|
|
61
62
|
ObjectBulkUpdateViewMixin,
|
|
62
63
|
ObjectChangeLogViewMixin,
|
|
64
|
+
ObjectDataComplianceViewMixin,
|
|
63
65
|
ObjectDestroyViewMixin,
|
|
64
66
|
ObjectDetailViewMixin,
|
|
65
67
|
ObjectEditViewMixin,
|
|
@@ -326,6 +328,7 @@ class ApprovalWorkflowUIViewSet(
|
|
|
326
328
|
section=SectionChoices.RIGHT_HALF,
|
|
327
329
|
exclude_columns=["approval_workflow"],
|
|
328
330
|
add_button_route=None,
|
|
331
|
+
enable_related_link=False,
|
|
329
332
|
),
|
|
330
333
|
],
|
|
331
334
|
)
|
|
@@ -412,6 +415,7 @@ class ApprovalWorkflowStageUIViewSet(
|
|
|
412
415
|
section=SectionChoices.RIGHT_HALF,
|
|
413
416
|
exclude_columns=["approval_workflow_stage"],
|
|
414
417
|
table_title="Responses",
|
|
418
|
+
enable_related_link=False,
|
|
415
419
|
),
|
|
416
420
|
],
|
|
417
421
|
)
|
|
@@ -757,6 +761,7 @@ class ConfigContextUIViewSet(NautobotUIViewSet):
|
|
|
757
761
|
"locations",
|
|
758
762
|
"roles",
|
|
759
763
|
"device_types",
|
|
764
|
+
"device_families",
|
|
760
765
|
"platforms",
|
|
761
766
|
"cluster_groups",
|
|
762
767
|
"clusters",
|
|
@@ -967,6 +972,7 @@ class ContactUIViewSet(NautobotUIViewSet):
|
|
|
967
972
|
table_filter="contact",
|
|
968
973
|
table_title="Contact For",
|
|
969
974
|
add_button_route=None,
|
|
975
|
+
enable_related_link=False,
|
|
970
976
|
),
|
|
971
977
|
),
|
|
972
978
|
)
|
|
@@ -1756,6 +1762,7 @@ class GraphQLQueryUIViewSet(
|
|
|
1756
1762
|
ObjectDestroyViewMixin,
|
|
1757
1763
|
ObjectBulkDestroyViewMixin,
|
|
1758
1764
|
ObjectChangeLogViewMixin,
|
|
1765
|
+
ObjectDataComplianceViewMixin,
|
|
1759
1766
|
ObjectNotesViewMixin,
|
|
1760
1767
|
):
|
|
1761
1768
|
filterset_form_class = forms.GraphQLQueryFilterForm
|
|
@@ -2042,55 +2049,66 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
2042
2049
|
ignore_singleton_lock = job_form.cleaned_data.pop("_ignore_singleton_lock", False)
|
|
2043
2050
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
2044
2051
|
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
)
|
|
2058
|
-
scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
|
|
2059
|
-
is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
|
|
2060
|
-
if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
|
|
2061
|
-
messages.error(
|
|
2062
|
-
request,
|
|
2063
|
-
"Unable to run or schedule job: "
|
|
2064
|
-
"This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
|
|
2065
|
-
"Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
|
|
2052
|
+
with transaction.atomic():
|
|
2053
|
+
scheduled_job = ScheduledJob.create_schedule(
|
|
2054
|
+
job_model,
|
|
2055
|
+
request.user,
|
|
2056
|
+
name=schedule_form.cleaned_data.get("_schedule_name"),
|
|
2057
|
+
start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
|
|
2058
|
+
interval=schedule_type,
|
|
2059
|
+
crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
|
|
2060
|
+
job_queue=job_queue,
|
|
2061
|
+
profile=profile,
|
|
2062
|
+
ignore_singleton_lock=ignore_singleton_lock,
|
|
2063
|
+
**job_class.serialize_data(job_form.cleaned_data),
|
|
2066
2064
|
)
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2065
|
+
scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
|
|
2066
|
+
is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
|
|
2067
|
+
if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
|
|
2068
|
+
messages.error(
|
|
2069
|
+
request,
|
|
2070
|
+
"Unable to run or schedule job: "
|
|
2071
|
+
"This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
|
|
2072
|
+
"Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
|
|
2073
|
+
)
|
|
2074
|
+
scheduled_job.delete()
|
|
2075
|
+
scheduled_job = None
|
|
2076
|
+
else:
|
|
2077
|
+
if dryrun and not is_scheduled:
|
|
2078
|
+
# Enqueue job for immediate execution when dryrun and (no schedule, no has_sensitive_variables)
|
|
2079
|
+
scheduled_job.delete()
|
|
2080
|
+
scheduled_job = None
|
|
2081
|
+
return self._handle_immediate_execution(
|
|
2082
|
+
request,
|
|
2083
|
+
job_model,
|
|
2084
|
+
job_class,
|
|
2085
|
+
job_form,
|
|
2086
|
+
profile,
|
|
2087
|
+
ignore_singleton_lock,
|
|
2088
|
+
job_queue,
|
|
2089
|
+
return_url,
|
|
2090
|
+
)
|
|
2091
|
+
# Step 1: Check if approval is required
|
|
2092
|
+
if scheduled_job_has_approval_workflow:
|
|
2093
|
+
return self._handle_approval_workflow_response(request, scheduled_job, return_url)
|
|
2094
|
+
|
|
2095
|
+
# Step 3: If approval is not required
|
|
2096
|
+
if is_scheduled:
|
|
2097
|
+
return self._handle_scheduled_job_response(request, scheduled_job, return_url)
|
|
2098
|
+
|
|
2099
|
+
# Step 4: Immediate execution (no schedule, no approval)
|
|
2100
|
+
scheduled_job.delete()
|
|
2101
|
+
scheduled_job = None
|
|
2070
2102
|
return self._handle_immediate_execution(
|
|
2071
|
-
request,
|
|
2103
|
+
request,
|
|
2104
|
+
job_model,
|
|
2105
|
+
job_class,
|
|
2106
|
+
job_form,
|
|
2107
|
+
profile,
|
|
2108
|
+
ignore_singleton_lock,
|
|
2109
|
+
job_queue,
|
|
2110
|
+
return_url,
|
|
2072
2111
|
)
|
|
2073
|
-
# Step 1: Check if approval is required
|
|
2074
|
-
if scheduled_job_has_approval_workflow:
|
|
2075
|
-
scheduled_job.validated_save()
|
|
2076
|
-
return self._handle_approval_workflow_response(request, scheduled_job, return_url)
|
|
2077
|
-
|
|
2078
|
-
# Step 3: If approval is not required
|
|
2079
|
-
if is_scheduled:
|
|
2080
|
-
scheduled_job.validated_save()
|
|
2081
|
-
return self._handle_scheduled_job_response(request, scheduled_job, return_url)
|
|
2082
|
-
|
|
2083
|
-
# Step 4: Immediate execution (no schedule, no approval)
|
|
2084
|
-
return self._handle_immediate_execution(
|
|
2085
|
-
request,
|
|
2086
|
-
job_model,
|
|
2087
|
-
job_class,
|
|
2088
|
-
job_form,
|
|
2089
|
-
profile,
|
|
2090
|
-
ignore_singleton_lock,
|
|
2091
|
-
job_queue,
|
|
2092
|
-
return_url,
|
|
2093
|
-
)
|
|
2094
2112
|
|
|
2095
2113
|
if return_url:
|
|
2096
2114
|
return redirect(return_url)
|
|
@@ -2139,13 +2157,17 @@ class JobView(generic.ObjectView):
|
|
|
2139
2157
|
section=SectionChoices.LEFT_HALF,
|
|
2140
2158
|
label="Job",
|
|
2141
2159
|
fields=["grouping", "name", "description", "enabled"],
|
|
2160
|
+
value_transforms={
|
|
2161
|
+
"description": [helpers.render_markdown],
|
|
2162
|
+
},
|
|
2142
2163
|
),
|
|
2143
2164
|
object_detail.ObjectsTablePanel(
|
|
2144
2165
|
weight=100,
|
|
2145
2166
|
section=SectionChoices.FULL_WIDTH,
|
|
2146
2167
|
table_class=tables.JobResultTable,
|
|
2147
|
-
table_title="
|
|
2148
|
-
table_filter=
|
|
2168
|
+
table_title="Job Results",
|
|
2169
|
+
table_filter="job_model",
|
|
2170
|
+
exclude_columns=["name", "job_model"],
|
|
2149
2171
|
),
|
|
2150
2172
|
jobs_ui.JobObjectFieldsPanel(
|
|
2151
2173
|
weight=100,
|
|
@@ -2501,7 +2523,7 @@ class SavedViewUIViewSet(
|
|
|
2501
2523
|
|
|
2502
2524
|
|
|
2503
2525
|
class ScheduledJobListView(generic.ObjectListView):
|
|
2504
|
-
queryset = ScheduledJob.objects.
|
|
2526
|
+
queryset = ScheduledJob.objects.all()
|
|
2505
2527
|
table = tables.ScheduledJobTable
|
|
2506
2528
|
filterset = filters.ScheduledJobFilterSet
|
|
2507
2529
|
filterset_form = forms.ScheduledJobFilterForm
|
|
@@ -2923,7 +2945,12 @@ class ObjectMetadataUIViewSet(
|
|
|
2923
2945
|
|
|
2924
2946
|
|
|
2925
2947
|
class NoteUIViewSet(
|
|
2926
|
-
|
|
2948
|
+
ObjectDestroyViewMixin,
|
|
2949
|
+
ObjectDetailViewMixin,
|
|
2950
|
+
ObjectEditViewMixin,
|
|
2951
|
+
ObjectListViewMixin,
|
|
2952
|
+
ObjectChangeLogViewMixin,
|
|
2953
|
+
ObjectDataComplianceViewMixin,
|
|
2927
2954
|
):
|
|
2928
2955
|
filterset_class = filters.NoteFilterSet
|
|
2929
2956
|
filterset_form_class = forms.NoteFilterForm
|
|
@@ -3250,6 +3277,7 @@ class SecretUIViewSet(
|
|
|
3250
3277
|
ObjectBulkDestroyViewMixin,
|
|
3251
3278
|
# no ObjectBulkUpdateViewMixin here yet
|
|
3252
3279
|
ObjectChangeLogViewMixin,
|
|
3280
|
+
ObjectDataComplianceViewMixin,
|
|
3253
3281
|
ObjectNotesViewMixin,
|
|
3254
3282
|
):
|
|
3255
3283
|
queryset = Secret.objects.all()
|
|
@@ -3331,7 +3359,8 @@ class SecretsGroupUIViewSet(NautobotUIViewSet):
|
|
|
3331
3359
|
object_detail.ObjectsTablePanel(
|
|
3332
3360
|
table_class=tables.SecretsGroupAssociationTable,
|
|
3333
3361
|
table_filter="secrets_group",
|
|
3334
|
-
related_field_name="
|
|
3362
|
+
related_field_name="secrets_groups",
|
|
3363
|
+
related_list_url_name="extras:secret_list",
|
|
3335
3364
|
table_title="Secrets",
|
|
3336
3365
|
section=SectionChoices.LEFT_HALF,
|
|
3337
3366
|
weight=200,
|
|
@@ -3437,6 +3466,7 @@ class TagUIViewSet(NautobotUIViewSet):
|
|
|
3437
3466
|
select_related_fields=["content_type"],
|
|
3438
3467
|
prefetch_related_fields=["content_object"],
|
|
3439
3468
|
include_paginator=True,
|
|
3469
|
+
enable_related_link=False,
|
|
3440
3470
|
),
|
|
3441
3471
|
),
|
|
3442
3472
|
)
|
|
@@ -3493,6 +3523,7 @@ class TeamUIViewSet(NautobotUIViewSet):
|
|
|
3493
3523
|
table_filter="team",
|
|
3494
3524
|
table_title="Contact For",
|
|
3495
3525
|
add_button_route=None,
|
|
3526
|
+
enable_related_link=False,
|
|
3496
3527
|
),
|
|
3497
3528
|
)
|
|
3498
3529
|
)
|
|
@@ -3525,7 +3556,7 @@ class WebhookUIViewSet(NautobotUIViewSet):
|
|
|
3525
3556
|
section=SectionChoices.LEFT_HALF,
|
|
3526
3557
|
weight=100,
|
|
3527
3558
|
fields=("http_method", "http_content_type", "payload_url", "additional_headers"),
|
|
3528
|
-
value_transforms={"additional_headers": [helpers.pre_tag]},
|
|
3559
|
+
value_transforms={"additional_headers": [partial(helpers.pre_tag, format_empty_value=False)]},
|
|
3529
3560
|
),
|
|
3530
3561
|
object_detail.ObjectFieldsPanel(
|
|
3531
3562
|
label="Security",
|
nautobot/ipam/filters.py
CHANGED
|
@@ -233,6 +233,13 @@ class PrefixFilterSet(
|
|
|
233
233
|
method="search_contains",
|
|
234
234
|
label="Prefixes which contain this prefix or IP",
|
|
235
235
|
)
|
|
236
|
+
ancestors = NaturalKeyOrPKMultipleChoiceFilter(
|
|
237
|
+
queryset=Prefix.objects.all(),
|
|
238
|
+
prefers_id=True,
|
|
239
|
+
to_field_name="network",
|
|
240
|
+
method="filter_ancestors",
|
|
241
|
+
label="Prefixes which are ancestors of this prefix (ID or host string)",
|
|
242
|
+
)
|
|
236
243
|
vrfs = NaturalKeyOrPKMultipleChoiceFilter(
|
|
237
244
|
queryset=VRF.objects.all(),
|
|
238
245
|
to_field_name="rd",
|
|
@@ -295,6 +302,10 @@ class PrefixFilterSet(
|
|
|
295
302
|
to_field_name="pk",
|
|
296
303
|
label="VPN Tunnel Endpoint ID",
|
|
297
304
|
)
|
|
305
|
+
vpn_tunnel_endpoints_name_contains = django_filters.CharFilter(
|
|
306
|
+
method="filter_vpntunnelendpoint_name_contains",
|
|
307
|
+
label="VPN Tunnel Endpoint Name Contains",
|
|
308
|
+
)
|
|
298
309
|
|
|
299
310
|
class Meta:
|
|
300
311
|
model = Prefix
|
|
@@ -345,6 +356,13 @@ class PrefixFilterSet(
|
|
|
345
356
|
prefixes_queryset |= queryset.filter(query)
|
|
346
357
|
return prefixes_queryset
|
|
347
358
|
|
|
359
|
+
def filter_ancestors(self, queryset, name, value):
|
|
360
|
+
if not value:
|
|
361
|
+
return queryset
|
|
362
|
+
prefixes = Prefix.objects.filter(pk__in=[v.id for v in value])
|
|
363
|
+
ancestor_ids = [ancestor.id for prefix in prefixes for ancestor in prefix.ancestors()]
|
|
364
|
+
return queryset.filter(pk__in=ancestor_ids)
|
|
365
|
+
|
|
348
366
|
def generate_query_filter_present_in_vrf(self, value):
|
|
349
367
|
if isinstance(value, (str, uuid.UUID)):
|
|
350
368
|
value = VRF.objects.get(pk=value)
|
|
@@ -358,6 +376,9 @@ class PrefixFilterSet(
|
|
|
358
376
|
params = self.generate_query_filter_present_in_vrf(value)
|
|
359
377
|
return queryset.filter(params).distinct()
|
|
360
378
|
|
|
379
|
+
def filter_vpntunnelendpoint_name_contains(self, queryset, name, value):
|
|
380
|
+
return queryset.filter(vpn_tunnel_endpoints__name__contains=value)
|
|
381
|
+
|
|
361
382
|
|
|
362
383
|
class PrefixLocationAssignmentFilterSet(NautobotFilterSet):
|
|
363
384
|
q = SearchFilter(
|
|
@@ -468,6 +489,11 @@ class IPAddressFilterSet(
|
|
|
468
489
|
label="Has NAT Inside",
|
|
469
490
|
)
|
|
470
491
|
ip_version = django_filters.NumberFilter()
|
|
492
|
+
services = NaturalKeyOrPKMultipleChoiceFilter(
|
|
493
|
+
queryset=Service.objects.all(),
|
|
494
|
+
to_field_name="name",
|
|
495
|
+
label="Services (name or ID)",
|
|
496
|
+
)
|
|
471
497
|
|
|
472
498
|
class Meta:
|
|
473
499
|
model = IPAddress
|
nautobot/ipam/tables.py
CHANGED
|
@@ -379,6 +379,11 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
379
379
|
cloud_networks_count = LinkedCountColumn(
|
|
380
380
|
viewname="cloud:cloudnetwork_list", url_params={"prefixes": "pk"}, verbose_name="Cloud Networks"
|
|
381
381
|
)
|
|
382
|
+
tunnel_endpoints_count = LinkedCountColumn(
|
|
383
|
+
viewname="vpn:vpntunnelendpoint_list",
|
|
384
|
+
url_params={"protected_prefixes": "pk"},
|
|
385
|
+
verbose_name="VPN Tunnel Endpoints",
|
|
386
|
+
)
|
|
382
387
|
actions = ButtonsColumn(Prefix)
|
|
383
388
|
|
|
384
389
|
class Meta(BaseTable.Meta):
|
|
@@ -394,6 +399,7 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
394
399
|
"tenant",
|
|
395
400
|
"location_count",
|
|
396
401
|
"cloud_networks_count",
|
|
402
|
+
"tunnel_endpoints_count",
|
|
397
403
|
"vlan",
|
|
398
404
|
"role",
|
|
399
405
|
"rir",
|
|
@@ -187,6 +187,7 @@ class PrefixTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilt
|
|
|
187
187
|
["status", "status__id"],
|
|
188
188
|
["status", "status__name"],
|
|
189
189
|
["type"],
|
|
190
|
+
["vpn_tunnel_endpoints", "vpn_tunnel_endpoints__id"],
|
|
190
191
|
)
|
|
191
192
|
|
|
192
193
|
def test_filters_generic(self):
|
|
@@ -230,6 +231,17 @@ class PrefixTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilt
|
|
|
230
231
|
all_prefixes = self.queryset.all()
|
|
231
232
|
self.assertQuerysetEqualAndNotEmpty(self.filterset(params, self.queryset).qs, all_prefixes)
|
|
232
233
|
|
|
234
|
+
def test_ancestors(self):
|
|
235
|
+
prefixes = (
|
|
236
|
+
Prefix.objects.create(prefix="10.0.0.0/8", status=Status.objects.get_for_model(Prefix).first()),
|
|
237
|
+
Prefix.objects.create(prefix="10.0.0.0/16", status=Status.objects.get_for_model(Prefix).first()),
|
|
238
|
+
Prefix.objects.create(prefix="10.0.0.0/24", status=Status.objects.get_for_model(Prefix).first()),
|
|
239
|
+
)
|
|
240
|
+
params = {"ancestors": [str(prefixes[2].pk)]}
|
|
241
|
+
filterset = self.filterset(params, self.queryset)
|
|
242
|
+
ancestors = [ancestor.id for ancestor in prefixes[2].ancestors()]
|
|
243
|
+
self.assertQuerysetEqualAndNotEmpty(filterset.qs, self.queryset.filter(id__in=ancestors))
|
|
244
|
+
|
|
233
245
|
|
|
234
246
|
class PrefixLocationAssignmentTestCase(FilterTestCases.FilterTestCase):
|
|
235
247
|
queryset = PrefixLocationAssignment.objects.all()
|
|
@@ -518,7 +530,11 @@ class IPAddressTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyF
|
|
|
518
530
|
queryset = IPAddress.objects.all()
|
|
519
531
|
filterset = IPAddressFilterSet
|
|
520
532
|
tenancy_related_name = "ip_addresses"
|
|
521
|
-
generic_filter_tests = (
|
|
533
|
+
generic_filter_tests = (
|
|
534
|
+
["nat_inside", "nat_inside__id"],
|
|
535
|
+
["services", "services__id"],
|
|
536
|
+
["services", "services__name"],
|
|
537
|
+
)
|
|
522
538
|
|
|
523
539
|
@classmethod
|
|
524
540
|
def setUpTestData(cls):
|
|
@@ -710,6 +726,15 @@ class IPAddressTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyF
|
|
|
710
726
|
nat_inside=ip1,
|
|
711
727
|
)
|
|
712
728
|
|
|
729
|
+
services = (
|
|
730
|
+
Service.objects.create(name="Service 1", protocol="TCP", ports=[80]),
|
|
731
|
+
Service.objects.create(name="Service 2", protocol="UDP", ports=[53]),
|
|
732
|
+
Service.objects.create(name="Service 3", protocol="TCP", ports=[443]),
|
|
733
|
+
)
|
|
734
|
+
services[0].ip_addresses.add(ip0)
|
|
735
|
+
services[1].ip_addresses.add(ip1)
|
|
736
|
+
services[2].ip_addresses.add(ip2)
|
|
737
|
+
|
|
713
738
|
def test_search(self):
|
|
714
739
|
ipv4_octets = self.ipv4_address.host.split(".")
|
|
715
740
|
ipv6_hextets = self.ipv6_address.host.split(":")
|
|
@@ -34,7 +34,7 @@ class IPAddressToInterfaceTest(TestCase):
|
|
|
34
34
|
|
|
35
35
|
@classmethod
|
|
36
36
|
def setUpTestData(cls):
|
|
37
|
-
cls.namespace = Namespace.objects.
|
|
37
|
+
cls.namespace = Namespace.objects.create(name="IPAM Models Test Namespace")
|
|
38
38
|
cls.status = Status.objects.get(name="Active")
|
|
39
39
|
cls.prefix = Prefix.objects.create(prefix="192.0.2.0/24", status=cls.status, namespace=cls.namespace)
|
|
40
40
|
cls.test_device = Device.objects.create(
|