nautobot 3.0.0a2__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 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +13 -1
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/cloud/filters.py +1 -1
- nautobot/cloud/navigation.py +3 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +6 -1
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +12 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
- nautobot/core/cli/migrate_deprecated_templates.py +227 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +62 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +35 -19
- nautobot/core/settings.yaml +17 -33
- nautobot/core/signals.py +12 -1
- nautobot/core/tables.py +13 -6
- nautobot/core/templates/40x.html +1 -1
- nautobot/core/templates/500.html +2 -2
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/buttons/export.html +1 -1
- nautobot/core/templates/components/button/dropdown.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +6 -7
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +22 -9
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +6 -5
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- 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_bulk_remove.html +2 -2
- nautobot/core/templates/generic/object_bulk_update.html +5 -4
- nautobot/core/templates/generic/object_create.html +5 -4
- 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_import.html +2 -1
- nautobot/core/templates/generic/object_list.html +12 -4
- nautobot/core/templates/generic/object_notes.html +5 -3
- nautobot/core/templates/generic/object_retrieve.html +4 -5
- nautobot/core/templates/graphene/graphiql.html +7 -8
- nautobot/core/templates/home.html +1 -1
- nautobot/core/templates/import_success.html +2 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/created_updated.html +7 -3
- nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +11 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +3 -9
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -11
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +17 -3
- nautobot/core/templates/system_jobs/import_objects.html +1 -2
- nautobot/core/templates/utilities/confirmation_form.html +2 -2
- nautobot/core/templates/utilities/obj_table.html +10 -2
- nautobot/core/templates/utilities/render_field.html +7 -7
- nautobot/core/templates/utilities/render_jinja2.html +2 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
- nautobot/core/templates/utilities/theme_preview.html +19 -3
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +76 -18
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +41 -17
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -13
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- 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/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +8 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +27 -28
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_forms.py +55 -13
- nautobot/core/tests/test_jobs.py +144 -3
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +76 -1
- nautobot/core/tests/test_patch_social_django.py +42 -0
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +62 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +123 -4
- nautobot/core/tests/test_utils.py +72 -5
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +70 -29
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +143 -27
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +143 -55
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +20 -9
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +28 -18
- nautobot/core/utils/lookup.py +49 -8
- nautobot/core/utils/module_loading.py +21 -0
- nautobot/core/utils/patch_social_django.py +128 -0
- nautobot/core/views/__init__.py +38 -1
- nautobot/core/views/generic.py +3 -3
- nautobot/core/views/mixins.py +45 -22
- nautobot/core/views/renderers.py +4 -3
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/apps.py +1 -5
- nautobot/data_validation/custom_validators.py +4 -4
- nautobot/data_validation/filters.py +1 -1
- nautobot/data_validation/forms.py +40 -0
- nautobot/data_validation/migrations/0001_initial.py +0 -7
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
- nautobot/data_validation/models.py +16 -7
- nautobot/data_validation/navigation.py +8 -1
- nautobot/data_validation/tables.py +12 -5
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +2 -2
- nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
- nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
- nautobot/data_validation/tests/test_filters.py +8 -6
- nautobot/data_validation/tests/test_models.py +15 -0
- nautobot/data_validation/tests/test_views.py +190 -32
- nautobot/data_validation/urls.py +2 -5
- nautobot/data_validation/views.py +73 -40
- nautobot/dcim/api/serializers.py +3 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +65 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
- nautobot/dcim/forms.py +90 -4
- 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/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
- 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 +98 -64
- nautobot/dcim/models/devices.py +30 -20
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +18 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +15 -15
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_trace.html +0 -4
- nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
- nautobot/dcim/templates/dcim/consoleport.html +7 -6
- nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_component_add.html +2 -2
- nautobot/dcim/templates/dcim/device_create.html +5 -3
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/device_list.html +73 -10
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/frontport.html +10 -9
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- 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 +35 -7
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- 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_list.html +57 -8
- 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/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +6 -5
- nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
- nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
- nautobot/dcim/templates/dcim/rearport.html +8 -7
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
- nautobot/dcim/tests/integration/test_controller.py +4 -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 +194 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +55 -7
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +328 -4
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +132 -29
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +777 -397
- nautobot/extras/api/views.py +60 -45
- nautobot/extras/choices.py +2 -13
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
- nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
- nautobot/extras/{filters/__init__.py → filters.py} +33 -48
- nautobot/extras/forms/forms.py +14 -15
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +11 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
- 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/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +33 -14
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +60 -4
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +23 -2
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/navigation.py +13 -9
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -9
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +64 -68
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/approval_dashboard.html +7 -5
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
- nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
- nautobot/extras/templates/extras/inc/json_format.html +10 -3
- nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
- nautobot/extras/templates/extras/job.html +29 -12
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
- nautobot/extras/templates/extras/job_edit.html +52 -46
- nautobot/extras/templates/extras/job_list.html +29 -25
- nautobot/extras/templates/extras/marketplace.html +5 -9
- nautobot/extras/templates/extras/object_configcontext.html +1 -1
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
- nautobot/extras/templates/extras/plugin_detail.html +29 -24
- nautobot/extras/templates/extras/plugins_list.html +16 -26
- nautobot/extras/templates/extras/role_retrieve.html +64 -0
- nautobot/extras/templates/extras/scheduledjob.html +4 -2
- nautobot/extras/templates/extras/secret_create.html +1 -1
- 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 +8 -9
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +9 -10
- nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +90 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +59 -5
- nautobot/extras/tests/test_customfields.py +22 -13
- nautobot/extras/tests/test_customfields_filters.py +479 -0
- nautobot/extras/tests/test_dynamicgroups.py +39 -1
- nautobot/extras/tests/test_filters.py +57 -22
- nautobot/extras/tests/test_forms.py +18 -21
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +1 -0
- nautobot/extras/tests/test_models.py +51 -33
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +52 -112
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +164 -71
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +53 -38
- 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 -2
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/tables.py +19 -6
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
- nautobot/ipam/templates/ipam/ipaddress.html +14 -0
- nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -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_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +13 -6
- nautobot/ipam/tests/test_filters.py +36 -1
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +44 -2
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +13 -14
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +53 -11
- 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/cabletrace.js +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -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/project-static/nautobot-icons/cloud.svg +1 -1
- nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
- nautobot/project-static/nautobot-icons/elements.svg +1 -1
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +1 -1
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +1 -1
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +1 -1
- nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
- nautobot/project-static/nautobot-icons/star.svg +1 -1
- 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/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +112 -30
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/users/templates/users/profile.html +45 -12
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/views.py +4 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/navigation.py +3 -2
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +45 -4
- nautobot/virtualization/views.py +4 -1
- 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 +219 -0
- nautobot/vpn/filters.py +234 -0
- nautobot/vpn/forms.py +487 -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 +535 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +383 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +336 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +293 -0
- nautobot/vpn/tests/test_models.py +147 -0
- nautobot/vpn/tests/test_views.py +300 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +495 -0
- nautobot/wireless/navigation.py +3 -2
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
- 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/template_content.py +0 -42
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/filters/mixins.py +0 -354
- 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 -243
- 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 -97
- 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/inc/prefix_header_extra_content_table.html +0 -4
- 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/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/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.test import tag
|
|
3
2
|
|
|
4
3
|
from nautobot.core.testing.integration import ObjectDetailsMixin, ObjectsListMixin, SeleniumTestCase
|
|
5
4
|
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
@@ -16,7 +15,6 @@ class ConfigContextSchemaTestCase(SeleniumTestCase, ObjectDetailsMixin, ObjectsL
|
|
|
16
15
|
super().setUp()
|
|
17
16
|
self.login_as_superuser()
|
|
18
17
|
|
|
19
|
-
@tag("fix_in_v3")
|
|
20
18
|
def test_create_valid_config_context_schema(self):
|
|
21
19
|
"""
|
|
22
20
|
Given a clean slate, navigate to and fill out the form for a valid schema object
|
|
@@ -34,13 +32,12 @@ class ConfigContextSchemaTestCase(SeleniumTestCase, ObjectDetailsMixin, ObjectsL
|
|
|
34
32
|
self.fill_input("name", "Integration Schema 1")
|
|
35
33
|
self.fill_input("description", "Description")
|
|
36
34
|
self.fill_input("data_schema", '{"type": "object", "properties": {"a": {"type": "string"}}}')
|
|
37
|
-
self.browser.
|
|
35
|
+
self.browser.find_by_xpath("//button[normalize-space()='Create']").click()
|
|
38
36
|
|
|
39
37
|
# Verify form redirect
|
|
40
38
|
self.assertTrue(self.browser.is_text_present("Created config context schema Integration Schema 1"))
|
|
41
39
|
self.assertTrue(self.browser.is_text_present("Edit"))
|
|
42
40
|
|
|
43
|
-
@tag("fix_in_v3")
|
|
44
41
|
def test_create_invalid_config_context_schema(self):
|
|
45
42
|
"""
|
|
46
43
|
Given a clean slate, navigate to and fill out the form for an invalid schema object
|
|
@@ -61,14 +58,13 @@ class ConfigContextSchemaTestCase(SeleniumTestCase, ObjectDetailsMixin, ObjectsL
|
|
|
61
58
|
self.fill_input("name", "Integration Schema 2")
|
|
62
59
|
self.fill_input("description", "Description")
|
|
63
60
|
self.fill_input("data_schema", '{"type": "object", "properties": {"a": {"type": "not a valid type"}}}')
|
|
64
|
-
self.browser.
|
|
61
|
+
self.browser.find_by_xpath("//button[normalize-space()='Create']").click()
|
|
65
62
|
|
|
66
63
|
# Verify validation error raised to user within form
|
|
67
64
|
self.assertTrue(self.browser.is_text_present("'not a valid type' is not valid under any of the given schemas"))
|
|
68
65
|
self.assertTrue(self.browser.is_text_present("Add a new config context schema"))
|
|
69
66
|
self.assertEqual(self.browser.find_by_name("name").first.value, "Integration Schema 2")
|
|
70
67
|
|
|
71
|
-
@tag("fix_in_v3")
|
|
72
68
|
def test_validation_tab(self):
|
|
73
69
|
"""
|
|
74
70
|
Given a config context schema that is assigned to a config context, and device, and a VM with valid context data
|
|
@@ -137,60 +133,65 @@ class ConfigContextSchemaTestCase(SeleniumTestCase, ObjectDetailsMixin, ObjectsL
|
|
|
137
133
|
|
|
138
134
|
# Assert Validation states
|
|
139
135
|
self.assertEqual(
|
|
140
|
-
len(self.browser.find_by_xpath("//div[@class[contains(., '
|
|
136
|
+
len(self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")), 3
|
|
141
137
|
) # 3 rows (config context, device, virtual machine)
|
|
142
|
-
for row in self.browser.find_by_xpath("//div[@class[contains(., '
|
|
138
|
+
for row in self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr"):
|
|
143
139
|
self.assertEqual(
|
|
144
140
|
row.find_by_tag("td")[-2].html,
|
|
145
|
-
'<span class="text-success"><i class="mdi mdi-check-bold" title="
|
|
141
|
+
'<span class="text-success"><i class="mdi mdi-check-bold" title="Yes"></i></span>',
|
|
146
142
|
)
|
|
147
143
|
|
|
148
144
|
# Edit the schema
|
|
149
|
-
self.
|
|
145
|
+
self.switch_tab("Config Context")
|
|
146
|
+
self.click_button("#edit-button")
|
|
150
147
|
# Change property "a" to be type string
|
|
151
148
|
self.fill_input(
|
|
152
149
|
"data_schema",
|
|
153
150
|
'{"type": "object", "properties": {"a": {"type": "string"}, "b": {"type": "integer"}, "c": {"type": "integer"}}, "additionalProperties": false}',
|
|
154
151
|
)
|
|
155
|
-
self.browser.
|
|
152
|
+
self.browser.find_by_xpath("//button[normalize-space()='Update']").click()
|
|
156
153
|
|
|
157
154
|
# Navigate to ConfigContextSchema Validation tab
|
|
158
|
-
self.
|
|
155
|
+
self.switch_tab("Validation")
|
|
159
156
|
|
|
160
157
|
# Assert Validation states
|
|
161
158
|
self.assertEqual(
|
|
162
|
-
len(self.browser.find_by_xpath("//div[@class[contains(., '
|
|
159
|
+
len(self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")), 3
|
|
163
160
|
) # 3 rows (config context, device, virtual machine)
|
|
164
|
-
for row in self.browser.find_by_xpath("//div[@class[contains(., '
|
|
161
|
+
for row in self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr"):
|
|
165
162
|
self.assertEqual(
|
|
166
163
|
row.find_by_tag("td")[-2].html,
|
|
167
|
-
'<span class="text-danger"><i class="mdi mdi-close-thick" title="
|
|
164
|
+
'<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span><span class="text-danger">123 is not of type \'string\'</span>',
|
|
168
165
|
)
|
|
169
166
|
|
|
170
167
|
# Edit the device local context data and redirect back to the validation tab
|
|
171
|
-
self.browser.find_by_xpath("//div[@class[contains(., '
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")[1].find_by_tag("td")[-1].find_by_tag(
|
|
169
|
+
"button"
|
|
170
|
+
).click()
|
|
171
|
+
menu = self.browser.find_by_xpath("//ul[contains(@class, 'dropdown-menu') and contains(@class, 'show')]")
|
|
172
|
+
menu.is_visible(wait_time=5)
|
|
173
|
+
menu.find_by_tag("a").click()
|
|
174
|
+
|
|
174
175
|
# Update the property "a" to be a string
|
|
175
176
|
self.fill_input("local_config_context_data", '{"a": "foo", "b": 456, "c": 777}')
|
|
176
|
-
self.browser.
|
|
177
|
+
self.browser.find_by_xpath("//button[normalize-space()='Update']").click()
|
|
177
178
|
|
|
178
179
|
# Assert Validation states
|
|
179
180
|
self.assertEqual(
|
|
180
|
-
len(self.browser.find_by_xpath("//div[@class[contains(., '
|
|
181
|
+
len(self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")), 3
|
|
181
182
|
) # 3 rows (config context, device, virtual machine)
|
|
182
183
|
# Config context still fails
|
|
183
184
|
self.assertEqual(
|
|
184
|
-
self.browser.find_by_xpath("//div[@class[contains(., '
|
|
185
|
-
'<span class="text-danger"><i class="mdi mdi-close-thick" title="
|
|
185
|
+
self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")[0].find_by_tag("td")[-2].html,
|
|
186
|
+
'<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span><span class="text-danger">123 is not of type \'string\'</span>',
|
|
186
187
|
)
|
|
187
188
|
# Device now passes
|
|
188
189
|
self.assertEqual(
|
|
189
|
-
self.browser.find_by_xpath("//div[@class[contains(., '
|
|
190
|
-
'<span class="text-success"><i class="mdi mdi-check-bold" title="
|
|
190
|
+
self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")[1].find_by_tag("td")[-2].html,
|
|
191
|
+
'<span class="text-success"><i class="mdi mdi-check-bold" title="Yes"></i></span>',
|
|
191
192
|
)
|
|
192
193
|
# Virtual machine still fails
|
|
193
194
|
self.assertEqual(
|
|
194
|
-
self.browser.find_by_xpath("//div[@class[contains(., '
|
|
195
|
-
'<span class="text-danger"><i class="mdi mdi-close-thick" title="
|
|
195
|
+
self.browser.find_by_xpath("//div[@class[contains(., 'card')]]//tbody/tr")[2].find_by_tag("td")[-2].html,
|
|
196
|
+
'<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span><span class="text-danger">123 is not of type \'string\'</span>',
|
|
196
197
|
)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.test import tag
|
|
3
2
|
from django.urls import reverse
|
|
4
3
|
from selenium.webdriver.common.keys import Keys
|
|
5
4
|
|
|
@@ -111,7 +110,11 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
111
110
|
self.assertEqual(len(table.find_by_css(".formset_row-custom_field_choices")), 5)
|
|
112
111
|
|
|
113
112
|
# And 6 after clicking "Add another..."
|
|
114
|
-
self.
|
|
113
|
+
self.click_button(".add-row")
|
|
114
|
+
# Before proceeding with further assertions and interactions, wait for the 6th row to appear in the DOM.
|
|
115
|
+
self.browser.is_element_present_by_css(
|
|
116
|
+
"#custom-field-choices .formset_row-custom_field_choices:nth-of-type(6)", wait_time=5
|
|
117
|
+
)
|
|
115
118
|
rows = table.find_by_css(".formset_row-custom_field_choices")
|
|
116
119
|
self.assertEqual(len(rows), 6)
|
|
117
120
|
self.fill_input("custom_field_choices-5-value", "choice3")
|
|
@@ -157,7 +160,6 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
157
160
|
self.assertTrue(self.browser.is_text_present("Modified custom field"))
|
|
158
161
|
self.assertTrue(self.browser.is_text_present("new_choice"))
|
|
159
162
|
|
|
160
|
-
@tag("fix_in_v3")
|
|
161
163
|
def test_update_type_select_create_delete_choices(self):
|
|
162
164
|
"""
|
|
163
165
|
Test edit existing field, deleting first choice, adding a new row and saving that as a new choice.
|
|
@@ -172,13 +174,13 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
172
174
|
|
|
173
175
|
# Gather the rows, delete the first one, add a new one.
|
|
174
176
|
table = self.browser.find_by_id("custom-field-choices")
|
|
175
|
-
self.
|
|
177
|
+
self.click_button(".add-row") # Add a new row
|
|
176
178
|
rows = table.find_by_css(".formset_row-custom_field_choices")
|
|
177
179
|
rows.first.find_by_css(".delete-row").click() # Delete first row
|
|
178
180
|
|
|
179
181
|
# Fill the new row, save it, assert correctness.
|
|
180
182
|
self.fill_input("custom_field_choices-5-value", "new_choice") # Fill the last row
|
|
181
|
-
self.browser.
|
|
183
|
+
self.browser.find_by_xpath("//button[normalize-space()='Update']").click()
|
|
182
184
|
self.assertEqual(self.browser.url, detail_url)
|
|
183
185
|
self.assertTrue(self.browser.is_text_present("Modified custom field"))
|
|
184
186
|
self.assertTrue(self.browser.is_text_present("new_choice"))
|
|
@@ -252,7 +254,6 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
252
254
|
# Confirm the JSON data is visible
|
|
253
255
|
self.assertTrue(self.browser.is_text_present("Test JSON Value"))
|
|
254
256
|
|
|
255
|
-
@tag("fix_in_v3")
|
|
256
257
|
def test_json_type_with_invalid_json(self):
|
|
257
258
|
"""
|
|
258
259
|
This test creates a custom field with a type of "json".
|
|
@@ -274,7 +275,7 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
274
275
|
active_web_element = self.browser.driver.switch_to.active_element
|
|
275
276
|
# Type invalid JSON data into the form
|
|
276
277
|
active_web_element.send_keys('{test_json_key: "Test Invalid JSON Value"}')
|
|
277
|
-
self.browser.find_by_xpath("
|
|
278
|
+
self.browser.find_by_xpath("//button[normalize-space()='Update']").click()
|
|
278
279
|
self.assertTrue(self.browser.is_text_present("Enter a valid JSON"))
|
|
279
280
|
|
|
280
281
|
def test_saving_object_after_its_custom_field_deleted(self):
|
|
@@ -303,9 +304,7 @@ class CustomFieldTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
303
304
|
# Visit the device edit page
|
|
304
305
|
self.browser.visit(f"{self.live_server_url}{reverse('dcim:device_edit', kwargs={'pk': device.pk})}")
|
|
305
306
|
# Get the first item selected on the custom field
|
|
306
|
-
self.
|
|
307
|
-
'document.querySelector(\'label:has(+ * [name="cf_test_selection_field"])\').scrollIntoView({ behavior: "instant" });'
|
|
308
|
-
)
|
|
307
|
+
self.scroll_element_into_view(css='label:has(+ * [name="cf_test_selection_field"])')
|
|
309
308
|
self.browser.find_by_xpath(".//label[contains(text(), 'Device Selection Field')]").click()
|
|
310
309
|
active_web_element = self.browser.driver.switch_to.active_element
|
|
311
310
|
active_web_element.send_keys(Keys.ENTER)
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.test import tag
|
|
3
2
|
from django.urls import reverse
|
|
4
3
|
from selenium.webdriver.common.keys import Keys
|
|
5
4
|
|
|
6
|
-
from nautobot.core.testing.integration import SeleniumTestCase
|
|
5
|
+
from nautobot.core.testing.integration import ObjectsListMixin, SeleniumTestCase
|
|
7
6
|
from nautobot.dcim.models import Device
|
|
8
7
|
from nautobot.extras.models import DynamicGroup
|
|
9
8
|
|
|
10
9
|
from . import create_test_device
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
class DynamicGroupTestCase(SeleniumTestCase):
|
|
12
|
+
class DynamicGroupTestCase(SeleniumTestCase, ObjectsListMixin):
|
|
14
13
|
"""
|
|
15
14
|
Integration test to check nautobot.extras.models.DynamicGroup add/edit functionality.
|
|
16
15
|
"""
|
|
@@ -19,7 +18,6 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
19
18
|
super().setUp()
|
|
20
19
|
self.login_as_superuser()
|
|
21
20
|
|
|
22
|
-
@tag("fix_in_v3")
|
|
23
21
|
def test_create_and_update(self):
|
|
24
22
|
"""
|
|
25
23
|
Test initial add and then update of a new DynamicGroup
|
|
@@ -32,7 +30,7 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
32
30
|
self.click_navbar_entry("Organization", "Dynamic Groups")
|
|
33
31
|
|
|
34
32
|
# Click add button
|
|
35
|
-
self.
|
|
33
|
+
self.click_add_item()
|
|
36
34
|
|
|
37
35
|
# Fill out the form.
|
|
38
36
|
name = "devices-active"
|
|
@@ -40,19 +38,20 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
40
38
|
self.browser.select("content_type", ct_label)
|
|
41
39
|
|
|
42
40
|
# Click that "Create" button
|
|
43
|
-
self.
|
|
41
|
+
self.click_edit_form_create_button()
|
|
44
42
|
|
|
45
43
|
# Verify form redirect and presence of content.
|
|
46
44
|
self.assertTrue(self.browser.is_text_present(f"Created dynamic group {name}"))
|
|
47
45
|
self.assertTrue(self.browser.is_text_present("Edit"))
|
|
48
46
|
|
|
49
47
|
# Edit the newly created DynamicGroup (Click that "Edit" button)
|
|
50
|
-
self.
|
|
48
|
+
self.click_button("#edit-button")
|
|
51
49
|
|
|
52
50
|
# Find the "Status" dynamic multi-select and type into it. Xpath is used
|
|
53
51
|
# to find the next "input" after the "status" select field.
|
|
54
52
|
status_field = self.browser.find_by_name("filter-status").first
|
|
55
53
|
status_input = status_field.find_by_xpath("./following::input[1]").first
|
|
54
|
+
self.scroll_element_into_view(element=status_input)
|
|
56
55
|
status_input.click() # Force focus on the input field to bring it on-screen
|
|
57
56
|
|
|
58
57
|
# Fill in "Status: Active".
|
|
@@ -61,7 +60,7 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
61
60
|
status_input.type(Keys.ENTER)
|
|
62
61
|
|
|
63
62
|
# Click that "Update" button
|
|
64
|
-
self.browser.
|
|
63
|
+
self.browser.find_by_xpath("//button[normalize-space()='Update']").click()
|
|
65
64
|
|
|
66
65
|
# Verify form redirect and presence of content.
|
|
67
66
|
self.assertTrue(self.browser.is_text_present(f"Modified dynamic group {name}"))
|
|
@@ -69,8 +68,12 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
69
68
|
|
|
70
69
|
# And just a cursory check to make sure that the filter worked.
|
|
71
70
|
group = DynamicGroup.objects.get(name=name)
|
|
72
|
-
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
73
71
|
self.assertEqual(group.filter, {"status": ["Active"]})
|
|
72
|
+
# Because we don't auto-refresh the members on UI create/update any more:
|
|
73
|
+
# TODO: a more complete integration test could click the "Refresh Members" JobButton, wait until the job completes,
|
|
74
|
+
# and so forth, rather than doing so programmatically here:
|
|
75
|
+
group.update_cached_members()
|
|
76
|
+
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
74
77
|
|
|
75
78
|
# Verify dynamic group shows up on device detail tab
|
|
76
79
|
self.browser.visit(
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import tempfile
|
|
4
4
|
|
|
5
5
|
from django.contrib.contenttypes.models import ContentType
|
|
6
|
-
from django.test import override_settings
|
|
6
|
+
from django.test import override_settings, tag
|
|
7
7
|
from django.urls import reverse
|
|
8
8
|
|
|
9
9
|
from nautobot.circuits.models import (
|
|
@@ -18,9 +18,8 @@ from nautobot.extras.choices import WebhookHttpMethodChoices
|
|
|
18
18
|
from nautobot.extras.context_managers import web_request_context
|
|
19
19
|
from nautobot.extras.models import Status, Webhook
|
|
20
20
|
|
|
21
|
-
from example_app.models import ExampleModel
|
|
22
|
-
|
|
23
21
|
|
|
22
|
+
@tag("example_app")
|
|
24
23
|
class AppWebhookTest(SeleniumTestCase):
|
|
25
24
|
"""
|
|
26
25
|
This test case proves that Apps can use the webhook functions when making changes on a model.
|
|
@@ -28,6 +27,9 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
28
27
|
|
|
29
28
|
def setUp(self):
|
|
30
29
|
super().setUp()
|
|
30
|
+
|
|
31
|
+
from example_app.models import ExampleModel
|
|
32
|
+
|
|
31
33
|
tempdir = tempfile.gettempdir()
|
|
32
34
|
for f in os.listdir(tempdir):
|
|
33
35
|
if f.startswith("test_app_webhook_"):
|
|
@@ -61,6 +63,8 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
61
63
|
"""
|
|
62
64
|
Test that webhooks are correctly triggered by an App model create.
|
|
63
65
|
"""
|
|
66
|
+
from example_app.models import ExampleModel
|
|
67
|
+
|
|
64
68
|
self.update_headers("test_app_webhook_create")
|
|
65
69
|
# Make change to model
|
|
66
70
|
with web_request_context(self.user):
|
|
@@ -73,6 +77,8 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
73
77
|
"""
|
|
74
78
|
Test that webhooks are correctly triggered by an App model update.
|
|
75
79
|
"""
|
|
80
|
+
from example_app.models import ExampleModel
|
|
81
|
+
|
|
76
82
|
self.update_headers("test_app_webhook_update")
|
|
77
83
|
obj = ExampleModel.objects.create(name="foo", number=100)
|
|
78
84
|
|
|
@@ -88,6 +94,8 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
88
94
|
"""
|
|
89
95
|
Test that webhooks are correctly triggered by an App model delete.
|
|
90
96
|
"""
|
|
97
|
+
from example_app.models import ExampleModel
|
|
98
|
+
|
|
91
99
|
self.update_headers(os.path.join(tempfile.gettempdir(), "test_app_webhook_delete"))
|
|
92
100
|
obj = ExampleModel.objects.create(name="foo", number=100)
|
|
93
101
|
|
|
@@ -102,6 +110,8 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
102
110
|
"""
|
|
103
111
|
Verify that webhook body_template is correctly used.
|
|
104
112
|
"""
|
|
113
|
+
from example_app.models import ExampleModel
|
|
114
|
+
|
|
105
115
|
self.update_headers("test_app_webhook_with_body")
|
|
106
116
|
|
|
107
117
|
self.webhook.body_template = '{"message": "{{ event }}"}'
|
|
@@ -117,6 +127,7 @@ class AppWebhookTest(SeleniumTestCase):
|
|
|
117
127
|
os.remove(os.path.join(tempfile.gettempdir(), "test_app_webhook_with_body"))
|
|
118
128
|
|
|
119
129
|
|
|
130
|
+
@tag("example_app")
|
|
120
131
|
class AppDocumentationTest(SeleniumTestCase):
|
|
121
132
|
"""
|
|
122
133
|
Integration tests for ensuring App provided docs are supported.
|
|
@@ -129,16 +140,16 @@ class AppDocumentationTest(SeleniumTestCase):
|
|
|
129
140
|
def test_object_edit_help_provided(self):
|
|
130
141
|
"""The ExampleModel object provides model documentation, this test ensures the help link is rendered."""
|
|
131
142
|
self.browser.visit(f"{self.live_server_url}{reverse('plugins:example_app:examplemodel_add')}")
|
|
132
|
-
|
|
133
|
-
self.assertTrue(self.browser.links.find_by_partial_href("example_app/docs/models/examplemodel.html"))
|
|
143
|
+
self.assertTrue(self.browser.links.find_by_partial_href("docs/example-app/models/examplemodel.html"))
|
|
134
144
|
|
|
135
145
|
def test_object_edit_help_not_provided(self):
|
|
136
146
|
"""The AnotherExampleModel object doesn't provide model documentation, this test ensures no help link is provided."""
|
|
137
147
|
self.browser.visit(f"{self.live_server_url}{reverse('plugins:example_app:anotherexamplemodel_add')}")
|
|
138
148
|
|
|
139
|
-
self.assertFalse(self.browser.links.find_by_partial_href("
|
|
149
|
+
self.assertFalse(self.browser.links.find_by_partial_href("/docs/example-app/models/anotherexamplemodel.html"))
|
|
140
150
|
|
|
141
151
|
|
|
152
|
+
@tag("example_app")
|
|
142
153
|
class AppReturnUrlTestCase(SeleniumTestCase):
|
|
143
154
|
"""
|
|
144
155
|
Integration tests for reversing App return urls.
|
|
@@ -159,6 +170,7 @@ class AppReturnUrlTestCase(SeleniumTestCase):
|
|
|
159
170
|
self.assertEqual(element["href"], f"{self.live_server_url}{reverse('plugins:example_app:examplemodel_list')}")
|
|
160
171
|
|
|
161
172
|
|
|
173
|
+
@tag("example_app")
|
|
162
174
|
class AppTabsTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
163
175
|
"""
|
|
164
176
|
Integration tests for extra object detail UI tabs.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.test import tag
|
|
3
2
|
from django.urls import reverse
|
|
4
3
|
|
|
5
4
|
from nautobot.core.testing.integration import ObjectDetailsMixin, SeleniumTestCase
|
|
@@ -19,7 +18,6 @@ class RelationshipsTestCase(SeleniumTestCase, ObjectDetailsMixin):
|
|
|
19
18
|
super().setUp()
|
|
20
19
|
self.login_as_superuser()
|
|
21
20
|
|
|
22
|
-
@tag("fix_in_v3")
|
|
23
21
|
def test_relationship_advanced_ui(self):
|
|
24
22
|
"""
|
|
25
23
|
This test creates a device and a relationship for that device.
|
|
@@ -10,7 +10,7 @@ from django.contrib.auth import get_user_model
|
|
|
10
10
|
from django.contrib.auth.models import Group
|
|
11
11
|
from django.contrib.contenttypes.models import ContentType
|
|
12
12
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
13
|
-
from django.test import override_settings
|
|
13
|
+
from django.test import override_settings, tag
|
|
14
14
|
from django.urls import reverse
|
|
15
15
|
from django.utils.timezone import make_aware, now
|
|
16
16
|
from rest_framework import status
|
|
@@ -144,7 +144,7 @@ class ApprovalWorkflowStageTest(
|
|
|
144
144
|
|
|
145
145
|
cls.approval_workflow_definitions = [
|
|
146
146
|
ApprovalWorkflowDefinition.objects.create(
|
|
147
|
-
name=f"Test Approval Workflow {i}", model_content_type=cls.scheduledjob_ct,
|
|
147
|
+
name=f"Test Approval Workflow {i}", model_content_type=cls.scheduledjob_ct, weight=i
|
|
148
148
|
)
|
|
149
149
|
for i in range(4)
|
|
150
150
|
]
|
|
@@ -160,7 +160,7 @@ class ApprovalWorkflowStageTest(
|
|
|
160
160
|
cls.approval_workflow_stage_definitions = [
|
|
161
161
|
ApprovalWorkflowStageDefinition.objects.create(
|
|
162
162
|
approval_workflow_definition=cls.approval_workflow_definitions[i],
|
|
163
|
-
|
|
163
|
+
sequence=i * 100,
|
|
164
164
|
name=f"Test Approval Workflow Stage {i} Definition",
|
|
165
165
|
min_approvers=1,
|
|
166
166
|
denial_message="Stage Denial Message",
|
|
@@ -403,7 +403,7 @@ class ApprovalWorkflowStageTest(
|
|
|
403
403
|
|
|
404
404
|
approval_workflow_stage_definition_2 = ApprovalWorkflowStageDefinition.objects.create(
|
|
405
405
|
approval_workflow_definition=approval_workflow.approval_workflow_definition,
|
|
406
|
-
|
|
406
|
+
sequence=200,
|
|
407
407
|
name="Approval Workflow Stage Definition 2",
|
|
408
408
|
min_approvers=1,
|
|
409
409
|
denial_message="Stage 2 Denial Message",
|
|
@@ -496,7 +496,7 @@ class ApprovalWorkflowStageTest(
|
|
|
496
496
|
|
|
497
497
|
approval_workflow_stage_definition_2 = ApprovalWorkflowStageDefinition.objects.create(
|
|
498
498
|
approval_workflow_definition=approval_workflow.approval_workflow_definition,
|
|
499
|
-
|
|
499
|
+
sequence=200,
|
|
500
500
|
name="Approval Workflow Stage Definition 2",
|
|
501
501
|
min_approvers=1,
|
|
502
502
|
denial_message="Stage 2 Denial Message",
|
|
@@ -645,7 +645,7 @@ class ApprovalWorkflowStageTest(
|
|
|
645
645
|
approver_group_2 = Group.objects.create(name="Approver Group 2")
|
|
646
646
|
approval_workflow_stage_definition_approver_group_2 = ApprovalWorkflowStageDefinition.objects.create(
|
|
647
647
|
approval_workflow_definition=self.approval_workflow_definitions[3],
|
|
648
|
-
|
|
648
|
+
sequence=100,
|
|
649
649
|
name="Test Approval Workflow Stage 1 Definition",
|
|
650
650
|
min_approvers=1,
|
|
651
651
|
denial_message="Stage Denial Message",
|
|
@@ -986,6 +986,9 @@ class ContactTest(APIViewTestCases.APIViewTestCase):
|
|
|
986
986
|
bulk_update_data = {
|
|
987
987
|
"address": "Carnegie Hall, New York, NY",
|
|
988
988
|
}
|
|
989
|
+
validation_excluded_fields = [
|
|
990
|
+
"teams", # M2M field, excluded by default
|
|
991
|
+
]
|
|
989
992
|
|
|
990
993
|
@classmethod
|
|
991
994
|
def setUpTestData(cls):
|
|
@@ -1793,6 +1796,7 @@ class GitRepositoryTest(APIViewTestCases.APIViewTestCase):
|
|
|
1793
1796
|
self.assertEqual(response.data["message"], f"Repository {self.repos[0].name} sync job added to queue.")
|
|
1794
1797
|
self.assertIsInstance(response.data["job_result"], dict)
|
|
1795
1798
|
|
|
1799
|
+
@tag("example_app")
|
|
1796
1800
|
def test_create_with_app_provided_contents(self):
|
|
1797
1801
|
"""Test that `provided_contents` published by an App works."""
|
|
1798
1802
|
self.add_permissions("extras.add_gitrepository")
|
|
@@ -2215,7 +2219,7 @@ class JobTest(
|
|
|
2215
2219
|
ApprovalWorkflowDefinition.objects.create(
|
|
2216
2220
|
name="Test Approval Workflow Definition 1",
|
|
2217
2221
|
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
2218
|
-
|
|
2222
|
+
weight=0,
|
|
2219
2223
|
)
|
|
2220
2224
|
|
|
2221
2225
|
# Do the stuff.
|
|
@@ -2248,6 +2252,69 @@ class JobTest(
|
|
|
2248
2252
|
self.assertTrue(schedule.approval_required)
|
|
2249
2253
|
self.assertEqual(schedule.kwargs["var4"], str(device_role.pk))
|
|
2250
2254
|
|
|
2255
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2256
|
+
@mock.patch("nautobot.extras.api.views.get_worker_count")
|
|
2257
|
+
@mock.patch("nautobot.extras.models.jobs.JobResult.enqueue_job")
|
|
2258
|
+
def test_run_job_filtered_approval(self, mock_enqueue_job, mock_get_worker_count):
|
|
2259
|
+
"""
|
|
2260
|
+
Run a job with a defined approval workflow whose filter should or should not include it.
|
|
2261
|
+
"""
|
|
2262
|
+
workflow = ApprovalWorkflowDefinition(
|
|
2263
|
+
name="Test Approval Workflow Definition 1",
|
|
2264
|
+
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
2265
|
+
weight=0,
|
|
2266
|
+
model_constraints={"job_model__job_class_name": "APITestJob"},
|
|
2267
|
+
)
|
|
2268
|
+
workflow.validated_save()
|
|
2269
|
+
|
|
2270
|
+
# Do the stuff.
|
|
2271
|
+
mock_get_worker_count.return_value = 1
|
|
2272
|
+
self.add_permissions("extras.run_job")
|
|
2273
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
2274
|
+
job_data = {
|
|
2275
|
+
"var1": "FooBar",
|
|
2276
|
+
"var2": 123,
|
|
2277
|
+
"var3": False,
|
|
2278
|
+
"var4": device_role.pk,
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
data = {
|
|
2282
|
+
"data": job_data,
|
|
2283
|
+
# schedule is omitted
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
url = self.get_run_url()
|
|
2287
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2288
|
+
self.assertHttpStatus(response, self.run_success_response_status)
|
|
2289
|
+
|
|
2290
|
+
# Assert that a JobResult for this job was NOT created.
|
|
2291
|
+
self.assertFalse(JobResult.objects.filter(name=self.job_model.name).exists())
|
|
2292
|
+
|
|
2293
|
+
# Assert that we have an immediate ScheduledJob and that it matches the job_model.
|
|
2294
|
+
schedule = ScheduledJob.objects.last()
|
|
2295
|
+
self.assertIsNotNone(schedule)
|
|
2296
|
+
self.assertEqual(schedule.interval, JobExecutionType.TYPE_FUTURE)
|
|
2297
|
+
self.assertTrue(schedule.approval_required)
|
|
2298
|
+
self.assertEqual(schedule.kwargs["var4"], str(device_role.pk))
|
|
2299
|
+
mock_enqueue_job.assert_not_called()
|
|
2300
|
+
|
|
2301
|
+
# Change the workflow definition so that it no longer applies to this job model
|
|
2302
|
+
workflow.model_constraints = {"job_model__job_class_name__istartswith": "SomeOtherJob"}
|
|
2303
|
+
workflow.validated_save()
|
|
2304
|
+
|
|
2305
|
+
mock_enqueue_job.return_value = None
|
|
2306
|
+
deserialized_data = self.job_class.deserialize_data(job_data)
|
|
2307
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2308
|
+
self.assertHttpStatus(response, self.run_success_response_status)
|
|
2309
|
+
expected_enqueue_job_args = (self.job_model, self.user)
|
|
2310
|
+
expected_enqueue_job_kwargs = {
|
|
2311
|
+
"job_queue": self.job_model.default_job_queue,
|
|
2312
|
+
**self.job_class.serialize_data(deserialized_data),
|
|
2313
|
+
}
|
|
2314
|
+
mock_enqueue_job.assert_called_with(*expected_enqueue_job_args, **expected_enqueue_job_kwargs)
|
|
2315
|
+
# No new scheduled job should be created
|
|
2316
|
+
self.assertEqual(schedule, ScheduledJob.objects.last())
|
|
2317
|
+
|
|
2251
2318
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2252
2319
|
@mock.patch("nautobot.extras.api.views.get_worker_count")
|
|
2253
2320
|
@mock.patch("nautobot.extras.models.jobs.JobResult.enqueue_job")
|
|
@@ -2481,13 +2548,14 @@ class JobTest(
|
|
|
2481
2548
|
"Unable to schedule job: Job may have sensitive input variables",
|
|
2482
2549
|
)
|
|
2483
2550
|
|
|
2551
|
+
@tag("example_app")
|
|
2484
2552
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2485
2553
|
@mock.patch("nautobot.extras.api.views.get_worker_count")
|
|
2486
2554
|
def test_run_a_job_with_sensitive_variables_when_approval_workflow_defined(self, mock_get_worker_count):
|
|
2487
2555
|
ApprovalWorkflowDefinition.objects.create(
|
|
2488
2556
|
name="Test Approval Workflow Definition 1",
|
|
2489
2557
|
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
2490
|
-
|
|
2558
|
+
weight=0,
|
|
2491
2559
|
)
|
|
2492
2560
|
|
|
2493
2561
|
mock_get_worker_count.return_value = 1
|
|
@@ -2702,7 +2770,7 @@ class JobTest(
|
|
|
2702
2770
|
ApprovalWorkflowDefinition.objects.create(
|
|
2703
2771
|
name="Test Approval Workflow Definition 1",
|
|
2704
2772
|
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
2705
|
-
|
|
2773
|
+
weight=0,
|
|
2706
2774
|
)
|
|
2707
2775
|
|
|
2708
2776
|
mock_get_worker_count.return_value = 1
|
|
@@ -2802,7 +2870,7 @@ class JobTest(
|
|
|
2802
2870
|
ApprovalWorkflowDefinition.objects.create(
|
|
2803
2871
|
name="Approval Definition",
|
|
2804
2872
|
model_content_type=ContentType.objects.get_for_model(ScheduledJob),
|
|
2805
|
-
|
|
2873
|
+
weight=0,
|
|
2806
2874
|
)
|
|
2807
2875
|
|
|
2808
2876
|
start_time = now() + timedelta(minutes=1)
|
|
@@ -3274,6 +3342,7 @@ class UserSavedViewAssociationTest(APIViewTestCases.APIViewTestCase):
|
|
|
3274
3342
|
class ScheduledJobTest(
|
|
3275
3343
|
APIViewTestCases.GetObjectViewTestCase,
|
|
3276
3344
|
APIViewTestCases.ListObjectsViewTestCase,
|
|
3345
|
+
APIViewTestCases.DeleteObjectViewTestCase,
|
|
3277
3346
|
):
|
|
3278
3347
|
model = ScheduledJob
|
|
3279
3348
|
choices_fields = []
|
|
@@ -4744,9 +4813,9 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|
|
4744
4813
|
data = {**self.create_data[0], "content_types": [Manufacturer._meta.label_lower]}
|
|
4745
4814
|
response = self.client.post(self._get_list_url(), data, format="json", **self.header)
|
|
4746
4815
|
|
|
4747
|
-
|
|
4816
|
+
tags = Tag.objects.filter(name=data["name"])
|
|
4748
4817
|
self.assertHttpStatus(response, 400)
|
|
4749
|
-
self.assertFalse(
|
|
4818
|
+
self.assertFalse(tags.exists())
|
|
4750
4819
|
self.assertIn(f"Invalid content type: {Manufacturer._meta.label_lower}", response.data["content_types"])
|
|
4751
4820
|
|
|
4752
4821
|
def test_create_tags_without_content_types(self):
|
|
@@ -4783,9 +4852,9 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|
|
4783
4852
|
"""Test updating a tag without changing its content-types."""
|
|
4784
4853
|
self.add_permissions("extras.change_tag")
|
|
4785
4854
|
|
|
4786
|
-
|
|
4787
|
-
tag_content_types = list(
|
|
4788
|
-
url = self._get_detail_url(
|
|
4855
|
+
tag_instance = Tag.objects.exclude(content_types=ContentType.objects.get_for_model(Location)).first()
|
|
4856
|
+
tag_content_types = list(tag_instance.content_types.all())
|
|
4857
|
+
url = self._get_detail_url(tag_instance)
|
|
4789
4858
|
data = {"color": ColorChoices.COLOR_LIME}
|
|
4790
4859
|
|
|
4791
4860
|
response = self.client.patch(url, data, format="json", **self.header)
|
|
@@ -4795,9 +4864,9 @@ class TagTest(APIViewTestCases.APIViewTestCase):
|
|
|
4795
4864
|
sorted(response.data["content_types"]), sorted([f"{ct.app_label}.{ct.model}" for ct in tag_content_types])
|
|
4796
4865
|
)
|
|
4797
4866
|
|
|
4798
|
-
|
|
4799
|
-
self.assertEqual(
|
|
4800
|
-
self.assertEqual(list(
|
|
4867
|
+
tag_instance.refresh_from_db()
|
|
4868
|
+
self.assertEqual(tag_instance.color, ColorChoices.COLOR_LIME)
|
|
4869
|
+
self.assertEqual(list(tag_instance.content_types.all()), tag_content_types)
|
|
4801
4870
|
|
|
4802
4871
|
|
|
4803
4872
|
#
|
|
@@ -4810,6 +4879,9 @@ class TeamTest(APIViewTestCases.APIViewTestCase):
|
|
|
4810
4879
|
bulk_update_data = {
|
|
4811
4880
|
"address": "Carnegie Hall, New York, NY",
|
|
4812
4881
|
}
|
|
4882
|
+
validation_excluded_fields = [
|
|
4883
|
+
"contacts", # M2M field, excluded by default
|
|
4884
|
+
]
|
|
4813
4885
|
|
|
4814
4886
|
@classmethod
|
|
4815
4887
|
def setUpTestData(cls):
|