nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/choices.py +0 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +9 -1
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit.html +1 -1
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/circuittype.html +1 -1
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- 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 +1 -0
- nautobot/core/api/views.py +8 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
- nautobot/core/cli/migrate_deprecated_templates.py +200 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/jobs/__init__.py +6 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/settings.py +18 -12
- nautobot/core/settings.yaml +13 -7
- 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/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- 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/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +2 -3
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +1 -1
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +4 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- 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_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 +2 -3
- nautobot/core/templates/graphene/graphiql.html +7 -7
- 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/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +1 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/nav_menu.html +2 -1
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/redoc_ui.html +7 -0
- 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 +4 -4
- nautobot/core/templates/utilities/theme_preview.html +16 -3
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +52 -6
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +23 -10
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/views.py +4 -0
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +9 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +6 -7
- 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 +75 -1
- 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_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +53 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +1 -1
- nautobot/core/tests/test_utils.py +31 -4
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +142 -10
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/object_detail.py +92 -12
- nautobot/core/urls.py +12 -1
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +17 -17
- nautobot/core/utils/lookup.py +3 -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 +15 -3
- nautobot/core/views/renderers.py +2 -0
- 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 +0 -12
- 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 +0 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +16 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
- nautobot/dcim/forms.py +1 -1
- nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
- nautobot/dcim/models/device_components.py +81 -68
- nautobot/dcim/models/devices.py +13 -16
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +3 -0
- nautobot/dcim/tables/template_code.py +14 -14
- nautobot/dcim/templates/dcim/cable.html +2 -61
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_edit.html +2 -5
- nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
- nautobot/dcim/templates/dcim/cable_trace.html +1 -3
- nautobot/dcim/templates/dcim/cable_update.html +5 -0
- nautobot/dcim/templates/dcim/consoleport.html +6 -5
- nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
- nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
- nautobot/dcim/templates/dcim/device/frontports.html +1 -1
- nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
- nautobot/dcim/templates/dcim/device/inventory.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
- nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/device/powerports.html +1 -1
- nautobot/dcim/templates/dcim/device/rearports.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device/wireless.html +1 -1
- 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_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- 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 +9 -8
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +26 -6
- nautobot/dcim/templates/dcim/interface_bulk_delete.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/location_retrieve.html +1 -242
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +5 -4
- nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
- nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
- nautobot/dcim/templates/dcim/rearport.html +7 -6
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
- nautobot/dcim/tests/integration/test_controller.py +1 -0
- nautobot/dcim/tests/test_api.py +8 -0
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +12 -6
- nautobot/dcim/tests/test_models.py +63 -4
- nautobot/dcim/tests/test_views.py +63 -22
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +547 -273
- nautobot/extras/api/views.py +9 -1
- nautobot/extras/choices.py +2 -13
- 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} +14 -46
- nautobot/extras/forms/forms.py +5 -13
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/management/__init__.py +9 -0
- 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/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +22 -13
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +59 -1
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +13 -0
- 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/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -4
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +52 -68
- 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/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield.html +1 -1
- 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/graphqlquery_retrieve.html +1 -96
- 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 -37
- nautobot/extras/templates/extras/plugin_detail.html +26 -21
- 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/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/tag.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_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +8 -7
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/test_api.py +27 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +35 -3
- 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 +21 -19
- 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 +13 -31
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_views.py +31 -30
- nautobot/extras/views.py +81 -19
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +27 -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 +13 -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_update.html +15 -0
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- 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 +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +43 -1
- 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 +46 -6
- nautobot/project-static/dist/css/nautobot.css +1 -1
- nautobot/project-static/dist/css/nautobot.css.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/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.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/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -0
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +61 -28
- 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/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.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_models.py +44 -4
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +209 -0
- nautobot/vpn/filters.py +233 -0
- nautobot/vpn/forms.py +486 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +527 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +380 -0
- nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +341 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +294 -0
- nautobot/vpn/tests/test_models.py +97 -0
- nautobot/vpn/tests/test_views.py +281 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +437 -0
- nautobot/wireless/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.0a3.dist-info}/METADATA +14 -14
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/data_validation/template_content.py +0 -42
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
|
@@ -10,16 +10,16 @@ from nautobot.extras.models import CustomLink
|
|
|
10
10
|
|
|
11
11
|
register = template.Library()
|
|
12
12
|
|
|
13
|
-
LINK_BUTTON = '<a href="{}"{} class="btn btn-
|
|
13
|
+
LINK_BUTTON = '<a href="{}"{} class="btn btn-{}">{}</a>\n'
|
|
14
14
|
GROUP_BUTTON = (
|
|
15
|
-
'<div class="
|
|
16
|
-
'<button type="button" class="btn btn-
|
|
17
|
-
'{} <span class="
|
|
15
|
+
'<div class="dropdown d-inline-flex align-middle">\n'
|
|
16
|
+
'<button type="button" class="btn btn-{} dropdown-toggle" data-bs-toggle="dropdown">\n'
|
|
17
|
+
'{} <span class="mdi mdi-chevron-down" aria-hidden="true"></span>\n'
|
|
18
18
|
"</button>\n"
|
|
19
|
-
'<ul class="dropdown-menu
|
|
19
|
+
'<ul class="dropdown-menu float-end">\n'
|
|
20
20
|
"{}</ul></div>\n"
|
|
21
21
|
)
|
|
22
|
-
GROUP_LINK = '<li><a href="{}"{}>{}</a></li>\n'
|
|
22
|
+
GROUP_LINK = '<li><a class="dropdown-item" href="{}"{}>{}</a></li>\n'
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
@register.simple_tag(takes_context=True)
|
|
@@ -58,12 +58,12 @@ def custom_links(context, obj):
|
|
|
58
58
|
link_rendered = render_jinja2(cl.target_url, link_context)
|
|
59
59
|
link_target = ' target="_blank"' if cl.new_window else ""
|
|
60
60
|
template_code += format_html(
|
|
61
|
-
LINK_BUTTON, link_rendered, link_target, cl.
|
|
61
|
+
LINK_BUTTON, link_rendered, link_target, cl.button_class_css_class, text_rendered
|
|
62
62
|
)
|
|
63
63
|
except Exception as e:
|
|
64
64
|
template_code += format_html(
|
|
65
|
-
'<a class="btn btn-
|
|
66
|
-
'<
|
|
65
|
+
'<a aria-disabled="true" class="btn btn-secondary disabled" title="{}">'
|
|
66
|
+
'<span class="mdi mdi-alert"></span> {}</a>\n',
|
|
67
67
|
e,
|
|
68
68
|
cl.name,
|
|
69
69
|
)
|
|
@@ -81,13 +81,13 @@ def custom_links(context, obj):
|
|
|
81
81
|
links_rendered += format_html(GROUP_LINK, link_rendered, link_target, text_rendered)
|
|
82
82
|
except Exception as e:
|
|
83
83
|
links_rendered += format_html(
|
|
84
|
-
'<li><a disabled="disabled" title="{}"><span class="text-secondary">'
|
|
85
|
-
'<
|
|
84
|
+
'<li><a aria-disabled="true" class="disabled dropdown-item" title="{}"><span class="text-secondary">'
|
|
85
|
+
'<span class="mdi mdi-alert"></span> {}</span></a></li>',
|
|
86
86
|
e,
|
|
87
87
|
cl.name,
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
if links_rendered:
|
|
91
|
-
template_code += format_html(GROUP_BUTTON, links[0].
|
|
91
|
+
template_code += format_html(GROUP_BUTTON, links[0].button_class_css_class, group, links_rendered)
|
|
92
92
|
|
|
93
93
|
return template_code
|
|
@@ -13,11 +13,11 @@ from nautobot.extras.models import Job, JobButton, JobQueue
|
|
|
13
13
|
register = template.Library()
|
|
14
14
|
|
|
15
15
|
GROUP_DROPDOWN = """
|
|
16
|
-
<div class="
|
|
17
|
-
<button type="button" class="btn btn-
|
|
18
|
-
{group_name} <span class="
|
|
16
|
+
<div class="dropdown d-inline-flex align-middle">
|
|
17
|
+
<button type="button" class="btn btn-{group_button_class} dropdown-toggle" data-bs-toggle="dropdown">
|
|
18
|
+
{group_name} <span class="mdi mdi-chevron-down" aria-hidden="true"></span>
|
|
19
19
|
</button>
|
|
20
|
-
<ul class="dropdown-menu
|
|
20
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
21
21
|
{grouped_buttons}
|
|
22
22
|
</ul>
|
|
23
23
|
</div>
|
|
@@ -33,7 +33,7 @@ HIDDEN_INPUTS = """
|
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
35
|
NO_CONFIRM_BUTTON = """
|
|
36
|
-
<button type="submit" form="form_id_{button_id}" class="btn btn-
|
|
36
|
+
<button type="submit" form="form_id_{button_id}" class="btn btn-{button_class} {menu_item}" {disabled}>{button_text}</button>
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
NO_CONFIRM_FORM = """
|
|
@@ -43,7 +43,7 @@ NO_CONFIRM_FORM = """
|
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
45
|
CONFIRM_BUTTON = """
|
|
46
|
-
<button type="button" class="btn btn-
|
|
46
|
+
<button type="button" class="btn btn-{button_class} {menu_item}" data-bs-toggle="modal" data-bs-target="#confirm_modal_id_{button_id}" {disabled}>
|
|
47
47
|
{button_text}
|
|
48
48
|
</button>
|
|
49
49
|
"""
|
|
@@ -53,8 +53,8 @@ CONFIRM_MODAL = """
|
|
|
53
53
|
<div class="modal-dialog" role="document">
|
|
54
54
|
<div class="modal-content">
|
|
55
55
|
<div class="modal-header">
|
|
56
|
-
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
|
57
56
|
<h4 class="modal-title" id="confirm_modal_label_{button_id}">Confirmation</h4>
|
|
57
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
58
58
|
</div>
|
|
59
59
|
<form id="form_id_{button_id}" action="{button_url}" method="post" class="form">
|
|
60
60
|
<div class="modal-body">
|
|
@@ -62,7 +62,7 @@ CONFIRM_MODAL = """
|
|
|
62
62
|
Run Job <strong>'{job}'</strong> with object <strong>'{object}'</strong>?
|
|
63
63
|
</div>
|
|
64
64
|
<div class="modal-footer">
|
|
65
|
-
<button type="button" class="btn btn-
|
|
65
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
66
66
|
<button type="submit" class="btn btn-primary">Confirm</button>
|
|
67
67
|
</div>
|
|
68
68
|
</form>
|
|
@@ -94,8 +94,9 @@ def _render_job_button_for_obj(job_button, obj, context, content_type):
|
|
|
94
94
|
except Exception as exc:
|
|
95
95
|
return (
|
|
96
96
|
format_html(
|
|
97
|
-
'<a class="btn btn-
|
|
98
|
-
"
|
|
97
|
+
'<a aria-disabled="true" class="btn btn-{} disabled" title="{}">'
|
|
98
|
+
'<span class="mdi mdi-alert"></span> {}</a>\n',
|
|
99
|
+
"secondary" if not job_button.group_name else "link dropdown-item",
|
|
99
100
|
exc,
|
|
100
101
|
job_button.name,
|
|
101
102
|
),
|
|
@@ -123,12 +124,13 @@ def _render_job_button_for_obj(job_button, obj, context, content_type):
|
|
|
123
124
|
template_args = {
|
|
124
125
|
"button_id": job_button.pk,
|
|
125
126
|
"button_text": text_rendered,
|
|
126
|
-
"button_class": job_button.
|
|
127
|
+
"button_class": job_button.button_class_css_class if not job_button.group_name else "link",
|
|
127
128
|
"button_url": reverse("extras:job_run", kwargs={"pk": job_button.job.pk}),
|
|
128
129
|
"object": obj,
|
|
129
130
|
"job": job_button.job,
|
|
130
131
|
"hidden_inputs": hidden_inputs,
|
|
131
132
|
"disabled": "" if (has_run_perm and job_button.job.installed and job_button.job.enabled) else "disabled",
|
|
133
|
+
"menu_item": "dropdown-item" if job_button.group_name else "",
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
if job_button.confirmation:
|
|
@@ -170,7 +172,7 @@ def job_buttons(context, obj):
|
|
|
170
172
|
|
|
171
173
|
# Add grouped buttons to template
|
|
172
174
|
for group_name, buttons in group_names.items():
|
|
173
|
-
group_button_class = buttons[0].
|
|
175
|
+
group_button_class = buttons[0].button_class_css_class
|
|
174
176
|
|
|
175
177
|
buttons_rendered = SAFE_EMPTY_STR
|
|
176
178
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from nautobot.core.celery import register_jobs
|
|
2
|
+
from nautobot.extras.jobs import get_task_logger, Job
|
|
3
|
+
|
|
4
|
+
logger = get_task_logger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestLogCountsByLevel(Job):
|
|
8
|
+
class Meta:
|
|
9
|
+
description = "Test counts of log entries by level."
|
|
10
|
+
|
|
11
|
+
def run(self): # pylint: disable=arguments-differ
|
|
12
|
+
# intentionally omit debug
|
|
13
|
+
logger.info("This is an info log")
|
|
14
|
+
logger.info("This is an info log")
|
|
15
|
+
logger.warning("This is a warning log")
|
|
16
|
+
logger.warning("This is a warning log")
|
|
17
|
+
logger.error("This is an error log")
|
|
18
|
+
logger.critical("This is a critical log")
|
|
19
|
+
logger.failure("This is a failure log")
|
|
20
|
+
# success log will be added by post_run
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
register_jobs(TestLogCountsByLevel)
|
|
@@ -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):
|
|
@@ -69,8 +69,12 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
69
69
|
|
|
70
70
|
# And just a cursory check to make sure that the filter worked.
|
|
71
71
|
group = DynamicGroup.objects.get(name=name)
|
|
72
|
-
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
73
72
|
self.assertEqual(group.filter, {"status": ["Active"]})
|
|
73
|
+
# Because we don't auto-refresh the members on UI create/update any more:
|
|
74
|
+
# TODO: a more complete integration test could click the "Refresh Members" JobButton, wait until the job completes,
|
|
75
|
+
# and so forth, rather than doing so programmatically here:
|
|
76
|
+
group.update_cached_members()
|
|
77
|
+
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
74
78
|
|
|
75
79
|
# Verify dynamic group shows up on device detail tab
|
|
76
80
|
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.
|