nautobot 3.0.0a3__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/choices.py +4 -0
- nautobot/apps/ui.py +4 -0
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/core/api/serializers.py +1 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +36 -13
- nautobot/core/cli/migrate_deprecated_templates.py +36 -9
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +56 -0
- nautobot/core/management/commands/generate_test_data.py +3 -3
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +17 -7
- nautobot/core/settings.yaml +4 -26
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/tab/content_wrapper.html +4 -4
- nautobot/core/templates/echarts/echarts.html +21 -8
- nautobot/core/templates/generic/object_bulk_create.html +2 -2
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/generic/object_retrieve.html +2 -2
- nautobot/core/templates/graphene/graphiql.html +0 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/header.html +10 -0
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +1 -8
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +0 -6
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +33 -0
- nautobot/core/templates/utilities/theme_preview.html +3 -0
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templatetags/helpers.py +24 -12
- nautobot/core/testing/integration.py +24 -13
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -17
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/runner.py +1 -2
- nautobot/core/tests/test_breadcrumbs.py +21 -21
- nautobot/core/tests/test_jobs.py +73 -6
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_templatetags_helpers.py +9 -0
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_ui.py +122 -3
- nautobot/core/tests/test_utils.py +41 -1
- nautobot/core/ui/breadcrumbs.py +68 -17
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +49 -65
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +54 -46
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +8 -8
- nautobot/core/utils/filtering.py +11 -1
- nautobot/core/utils/lookup.py +46 -0
- nautobot/core/views/mixins.py +31 -20
- nautobot/core/views/renderers.py +2 -3
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -2
- nautobot/dcim/api/serializers.py +3 -0
- nautobot/dcim/choices.py +49 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filters.py +13 -1
- nautobot/dcim/forms.py +89 -3
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/{0078_remove_device_location_tenant_name_uniqueness.py → 0079_remove_device_location_tenant_name_uniqueness.py} +1 -1
- nautobot/dcim/migrations/{0079_device_name_data_migration.py → 0080_device_name_data_migration.py} +1 -1
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +22 -1
- nautobot/dcim/models/devices.py +17 -4
- nautobot/dcim/tables/devices.py +15 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +1 -1
- nautobot/dcim/templates/dcim/cable_trace.html +0 -2
- nautobot/dcim/templates/dcim/consoleport.html +1 -1
- nautobot/dcim/templates/dcim/consoleserverport.html +1 -1
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/frontport.html +1 -1
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +9 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +1 -1
- nautobot/dcim/templates/dcim/rack_elevation_list.html +1 -1
- nautobot/dcim/templates/dcim/rack_retrieve.html +0 -11
- nautobot/dcim/templates/dcim/rearport.html +1 -1
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_update.html +1 -1
- nautobot/dcim/tests/integration/test_controller.py +3 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +186 -6
- nautobot/dcim/tests/test_filters.py +43 -1
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +265 -0
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +69 -7
- nautobot/dcim/views.py +232 -126
- nautobot/extras/api/views.py +51 -44
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/filters.py +19 -2
- nautobot/extras/forms/forms.py +9 -2
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +2 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/approvals.py +11 -1
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/jobs.py +1 -3
- nautobot/extras/models/models.py +10 -2
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/views.py +0 -5
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/tables.py +12 -0
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/objectchange_retrieve.html +0 -2
- nautobot/extras/templates/extras/plugin_detail.html +3 -3
- nautobot/extras/templates/extras/secret_create.html +1 -1
- nautobot/extras/tests/integration/test_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_customfields.py +1 -3
- nautobot/extras/tests/integration/test_dynamicgroups.py +7 -8
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +63 -0
- nautobot/extras/tests/test_changelog.py +24 -2
- nautobot/extras/tests/test_filters.py +36 -3
- nautobot/extras/tests/test_models.py +38 -2
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +22 -83
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +83 -52
- nautobot/ipam/filters.py +26 -0
- nautobot/ipam/tables.py +6 -0
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/tests/test_filters.py +26 -1
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/ipam/views.py +9 -7
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/nautobot.js +0 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/nautobot.scss +51 -2
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +1 -0
- nautobot/virtualization/views.py +4 -1
- nautobot/vpn/factory.py +25 -15
- nautobot/vpn/filters.py +1 -0
- nautobot/vpn/forms.py +1 -0
- nautobot/vpn/migrations/0001_initial.py +1 -1
- nautobot/vpn/models.py +16 -8
- nautobot/vpn/tables.py +5 -2
- nautobot/vpn/tests/test_api.py +0 -5
- nautobot/vpn/tests/test_forms.py +1 -2
- nautobot/vpn/tests/test_models.py +57 -7
- nautobot/vpn/tests/test_views.py +22 -3
- nautobot/vpn/views.py +78 -20
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +4 -4
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +243 -352
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/cable.html +0 -2
- nautobot/dcim/templates/dcim/cable_edit.html +0 -2
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -2
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/vpn/templates/vpn/vpnprofile.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
- {nautobot-3.0.0a3.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +0 -0
|
@@ -18,7 +18,9 @@ from nautobot.dcim.choices import (
|
|
|
18
18
|
ConsolePortTypeChoices,
|
|
19
19
|
DeviceFaceChoices,
|
|
20
20
|
DeviceUniquenessChoices,
|
|
21
|
+
InterfaceDuplexChoices,
|
|
21
22
|
InterfaceModeChoices,
|
|
23
|
+
InterfaceSpeedChoices,
|
|
22
24
|
InterfaceTypeChoices,
|
|
23
25
|
PortTypeChoices,
|
|
24
26
|
PowerFeedBreakerPoleChoices,
|
|
@@ -727,6 +729,101 @@ class InterfaceTemplateTestCase(ModularDeviceComponentTemplateTestCaseMixin, Tes
|
|
|
727
729
|
first_status = Status.objects.get_for_model(Interface).first()
|
|
728
730
|
self.assertIsNotNone(device_2.interfaces.get(name="Test_Template_1").status, first_status)
|
|
729
731
|
|
|
732
|
+
def test_speed_disallowed_for_lag_virtual_wireless(self):
|
|
733
|
+
"""speed must be None for LAG, virtual, and wireless templates."""
|
|
734
|
+
manufacturer = Manufacturer.objects.first()
|
|
735
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="SpeedGuard 1000")
|
|
736
|
+
|
|
737
|
+
for if_type in (
|
|
738
|
+
InterfaceTypeChoices.TYPE_LAG,
|
|
739
|
+
InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
740
|
+
InterfaceTypeChoices.TYPE_80211N,
|
|
741
|
+
):
|
|
742
|
+
with self.subTest(if_type=if_type):
|
|
743
|
+
with self.assertRaises(ValidationError) as cm:
|
|
744
|
+
InterfaceTemplate(
|
|
745
|
+
device_type=device_type,
|
|
746
|
+
name=f"bad-{if_type}",
|
|
747
|
+
type=if_type,
|
|
748
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
749
|
+
).full_clean()
|
|
750
|
+
self.assertIn("Speed is not applicable to this interface type.", str(cm.exception))
|
|
751
|
+
|
|
752
|
+
def test_duplex_disallowed_for_lag_virtual_wireless(self):
|
|
753
|
+
"""duplex must be blank for LAG, virtual, and wireless templates."""
|
|
754
|
+
manufacturer = Manufacturer.objects.first()
|
|
755
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="DuplexGuard 1000")
|
|
756
|
+
|
|
757
|
+
for itype in (
|
|
758
|
+
InterfaceTypeChoices.TYPE_LAG,
|
|
759
|
+
InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
760
|
+
InterfaceTypeChoices.TYPE_80211N,
|
|
761
|
+
):
|
|
762
|
+
with self.assertRaises(ValidationError):
|
|
763
|
+
InterfaceTemplate(
|
|
764
|
+
device_type=device_type,
|
|
765
|
+
name=f"bad-{itype}",
|
|
766
|
+
type=itype,
|
|
767
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
768
|
+
).full_clean()
|
|
769
|
+
|
|
770
|
+
def test_duplex_disallowed_for_non_base_t(self):
|
|
771
|
+
"""duplex must be blank for non-BASE-T physical types (e.g., SFP)."""
|
|
772
|
+
manufacturer = Manufacturer.objects.first()
|
|
773
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="SfpGuard 1000")
|
|
774
|
+
|
|
775
|
+
with self.assertRaises(ValidationError) as cm:
|
|
776
|
+
InterfaceTemplate(
|
|
777
|
+
device_type=device_type,
|
|
778
|
+
name="sfp0",
|
|
779
|
+
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
780
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
781
|
+
).full_clean()
|
|
782
|
+
self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", str(cm.exception))
|
|
783
|
+
|
|
784
|
+
def test_duplex_and_speed_allowed_for_base_t(self):
|
|
785
|
+
"""BASE-T physical types accept duplex and speed values."""
|
|
786
|
+
manufacturer = Manufacturer.objects.first()
|
|
787
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="CopperOK 1000")
|
|
788
|
+
|
|
789
|
+
tmpl = InterfaceTemplate(
|
|
790
|
+
device_type=device_type,
|
|
791
|
+
name="eth0",
|
|
792
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
793
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
794
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
795
|
+
)
|
|
796
|
+
tmpl.full_clean() # should not raise
|
|
797
|
+
|
|
798
|
+
def test_instantiation_propagates_speed_and_duplex(self):
|
|
799
|
+
"""Interface created from template inherits speed and duplex."""
|
|
800
|
+
statuses = Status.objects.get_for_model(Device)
|
|
801
|
+
location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
|
|
802
|
+
manufacturer = Manufacturer.objects.first()
|
|
803
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
804
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="Propagate 2000")
|
|
805
|
+
|
|
806
|
+
InterfaceTemplate.objects.create(
|
|
807
|
+
device_type=device_type,
|
|
808
|
+
name="EthX",
|
|
809
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
810
|
+
mgmt_only=False,
|
|
811
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
812
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
device = Device.objects.create(
|
|
816
|
+
device_type=device_type,
|
|
817
|
+
role=device_role,
|
|
818
|
+
status=statuses[0],
|
|
819
|
+
name="Device-Prop",
|
|
820
|
+
location=location,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
iface = device.interfaces.get(name="EthX")
|
|
824
|
+
self.assertEqual(iface.speed, InterfaceSpeedChoices.SPEED_1G)
|
|
825
|
+
self.assertEqual(iface.duplex, InterfaceDuplexChoices.DUPLEX_FULL)
|
|
826
|
+
|
|
730
827
|
|
|
731
828
|
class InterfaceRedundancyGroupTestCase(ModelTestCases.BaseModelTestCase):
|
|
732
829
|
model = InterfaceRedundancyGroup
|
|
@@ -2779,6 +2876,7 @@ class InterfaceTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
|
|
|
2779
2876
|
name="VLAN 1", vid=100, location=location, status=vlan_status, vlan_group=vlan_group
|
|
2780
2877
|
)
|
|
2781
2878
|
status = Status.objects.get_for_model(Device).first()
|
|
2879
|
+
cls.intf_status = Status.objects.get_for_model(Interface).first()
|
|
2782
2880
|
cls.device = Device.objects.create(
|
|
2783
2881
|
name="Device 1",
|
|
2784
2882
|
device_type=devicetype,
|
|
@@ -3008,6 +3106,173 @@ class InterfaceTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
|
|
|
3008
3106
|
self.device.refresh_from_db()
|
|
3009
3107
|
self.assertEqual(self.device.primary_ip6, None)
|
|
3010
3108
|
|
|
3109
|
+
def _assert_invalid_speed_duplex(self, if_type, speed=None, duplex="", expected_error=""):
|
|
3110
|
+
iface = Interface(
|
|
3111
|
+
device=self.device,
|
|
3112
|
+
name=f"test-{if_type}",
|
|
3113
|
+
type=if_type,
|
|
3114
|
+
status=self.intf_status,
|
|
3115
|
+
speed=speed,
|
|
3116
|
+
duplex=duplex,
|
|
3117
|
+
)
|
|
3118
|
+
with self.assertRaises(ValidationError) as cm:
|
|
3119
|
+
iface.full_clean()
|
|
3120
|
+
self.assertIn(expected_error, str(cm.exception))
|
|
3121
|
+
|
|
3122
|
+
def test_disallowed_speed_and_duplex_matrix(self):
|
|
3123
|
+
"""Test that interface types with no speed or duplex disallow those settings."""
|
|
3124
|
+
test_cases = [
|
|
3125
|
+
# LAG
|
|
3126
|
+
(
|
|
3127
|
+
InterfaceTypeChoices.TYPE_LAG,
|
|
3128
|
+
InterfaceSpeedChoices.SPEED_1M,
|
|
3129
|
+
None,
|
|
3130
|
+
"Speed is not applicable to this interface type.",
|
|
3131
|
+
),
|
|
3132
|
+
(
|
|
3133
|
+
InterfaceTypeChoices.TYPE_LAG,
|
|
3134
|
+
None,
|
|
3135
|
+
InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3136
|
+
"Duplex is not applicable to this interface type.",
|
|
3137
|
+
),
|
|
3138
|
+
# Virtual
|
|
3139
|
+
(
|
|
3140
|
+
InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
3141
|
+
InterfaceSpeedChoices.SPEED_1M,
|
|
3142
|
+
None,
|
|
3143
|
+
"Speed is not applicable to this interface type.",
|
|
3144
|
+
),
|
|
3145
|
+
(
|
|
3146
|
+
InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
3147
|
+
None,
|
|
3148
|
+
InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3149
|
+
"Duplex is not applicable to this interface type.",
|
|
3150
|
+
),
|
|
3151
|
+
# Wireless
|
|
3152
|
+
(
|
|
3153
|
+
InterfaceTypeChoices.TYPE_80211AC,
|
|
3154
|
+
InterfaceSpeedChoices.SPEED_1M,
|
|
3155
|
+
None,
|
|
3156
|
+
"Speed is not applicable to this interface type.",
|
|
3157
|
+
),
|
|
3158
|
+
(
|
|
3159
|
+
InterfaceTypeChoices.TYPE_80211AC,
|
|
3160
|
+
None,
|
|
3161
|
+
InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3162
|
+
"Duplex is not applicable to this interface type.",
|
|
3163
|
+
),
|
|
3164
|
+
# Copper (negative speed is invalid)
|
|
3165
|
+
(InterfaceTypeChoices.TYPE_1GE_FIXED, -100, None, "Ensure this value is greater than or equal to 0."),
|
|
3166
|
+
# Copper (speed as a string is invalid)
|
|
3167
|
+
(InterfaceTypeChoices.TYPE_1GE_FIXED, "100 Mbps", None, "value must be an integer."),
|
|
3168
|
+
# Copper (invalid duplex is invalid)
|
|
3169
|
+
(
|
|
3170
|
+
InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
3171
|
+
InterfaceSpeedChoices.SPEED_1M,
|
|
3172
|
+
"invalid",
|
|
3173
|
+
"Value 'invalid' is not a valid choice.",
|
|
3174
|
+
),
|
|
3175
|
+
# Optical (no duplex allowed)
|
|
3176
|
+
(
|
|
3177
|
+
InterfaceTypeChoices.TYPE_10GE_SFP_PLUS,
|
|
3178
|
+
InterfaceSpeedChoices.SPEED_1M,
|
|
3179
|
+
InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3180
|
+
"Duplex is only applicable to copper twisted-pair interfaces.",
|
|
3181
|
+
),
|
|
3182
|
+
]
|
|
3183
|
+
for if_type, speed, duplex, expected_error in test_cases:
|
|
3184
|
+
with self.subTest(f"{if_type} with speed={speed} and duplex={duplex}"):
|
|
3185
|
+
self._assert_invalid_speed_duplex(if_type, speed, duplex, expected_error)
|
|
3186
|
+
|
|
3187
|
+
def test_copper_allows_duplex_and_non_negative_speed(self):
|
|
3188
|
+
"""Test that copper interfaces allow duplex and non-negative speed."""
|
|
3189
|
+
iface = Interface(
|
|
3190
|
+
device=self.device,
|
|
3191
|
+
name="eth1",
|
|
3192
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED, # 1000BASE-T
|
|
3193
|
+
status=self.intf_status,
|
|
3194
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
3195
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3196
|
+
)
|
|
3197
|
+
# Should not raise
|
|
3198
|
+
iface.full_clean()
|
|
3199
|
+
|
|
3200
|
+
iface.speed = 0
|
|
3201
|
+
iface.full_clean()
|
|
3202
|
+
|
|
3203
|
+
def test_lag_allows_no_speed_or_duplex(self):
|
|
3204
|
+
"""Test that LAG interfaces pass validation when speed and duplex are not set."""
|
|
3205
|
+
iface = Interface(
|
|
3206
|
+
device=self.device,
|
|
3207
|
+
name="Port-Channel1",
|
|
3208
|
+
type=InterfaceTypeChoices.TYPE_LAG,
|
|
3209
|
+
status=self.intf_status,
|
|
3210
|
+
)
|
|
3211
|
+
# Should not raise when speed and duplex are not set
|
|
3212
|
+
iface.full_clean()
|
|
3213
|
+
|
|
3214
|
+
def test_optical_disallows_duplex_allows_speed(self):
|
|
3215
|
+
"""Test that optical interfaces do not allow duplex and allow positive speed."""
|
|
3216
|
+
# Duplex set should error
|
|
3217
|
+
iface_bad = Interface(
|
|
3218
|
+
device=self.device,
|
|
3219
|
+
name="xe0",
|
|
3220
|
+
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS,
|
|
3221
|
+
status=self.intf_status,
|
|
3222
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3223
|
+
)
|
|
3224
|
+
with self.assertRaises(ValidationError) as cm:
|
|
3225
|
+
iface_bad.full_clean()
|
|
3226
|
+
self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", str(cm.exception))
|
|
3227
|
+
|
|
3228
|
+
# Speed positive should pass
|
|
3229
|
+
iface_ok = Interface(
|
|
3230
|
+
device=self.device,
|
|
3231
|
+
name="xe1",
|
|
3232
|
+
type=InterfaceTypeChoices.TYPE_10GE_SFP_PLUS,
|
|
3233
|
+
status=self.intf_status,
|
|
3234
|
+
speed=InterfaceSpeedChoices.SPEED_10G,
|
|
3235
|
+
)
|
|
3236
|
+
iface_ok.full_clean()
|
|
3237
|
+
|
|
3238
|
+
def test_changing_copper_interface_with_speed_and_duplex_to_optical_fails(self):
|
|
3239
|
+
"""Test that changing a copper interface with speed and duplex to an optical interface fails."""
|
|
3240
|
+
|
|
3241
|
+
with self.subTest("speed"):
|
|
3242
|
+
iface = Interface(
|
|
3243
|
+
device=self.device,
|
|
3244
|
+
name="eth3",
|
|
3245
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
3246
|
+
status=self.intf_status,
|
|
3247
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
3248
|
+
)
|
|
3249
|
+
iface.full_clean()
|
|
3250
|
+
|
|
3251
|
+
iface.type = InterfaceTypeChoices.TYPE_LAG
|
|
3252
|
+
with self.assertRaises(ValidationError) as cm:
|
|
3253
|
+
iface.full_clean()
|
|
3254
|
+
self.assertIn("Speed is not applicable to this interface type.", str(cm.exception))
|
|
3255
|
+
|
|
3256
|
+
with self.subTest("duplex"):
|
|
3257
|
+
iface = Interface(
|
|
3258
|
+
device=self.device,
|
|
3259
|
+
name="eth3",
|
|
3260
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
3261
|
+
status=self.intf_status,
|
|
3262
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
3263
|
+
)
|
|
3264
|
+
iface.full_clean()
|
|
3265
|
+
|
|
3266
|
+
iface.type = InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
|
3267
|
+
with self.assertRaises(ValidationError) as cm:
|
|
3268
|
+
iface.full_clean()
|
|
3269
|
+
self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", str(cm.exception))
|
|
3270
|
+
|
|
3271
|
+
iface.type = InterfaceTypeChoices.TYPE_10GE_SFP_PLUS
|
|
3272
|
+
with self.assertRaises(ValidationError) as cm:
|
|
3273
|
+
iface.full_clean()
|
|
3274
|
+
self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", str(cm.exception))
|
|
3275
|
+
|
|
3011
3276
|
|
|
3012
3277
|
class SoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
|
|
3013
3278
|
model = SoftwareImageFile
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
from nautobot.dcim.choices import InterfaceDuplexChoices, InterfaceSpeedChoices, InterfaceTypeChoices
|
|
4
|
+
from nautobot.dcim.models import Device, DeviceType, Interface, InterfaceTemplate, Location, LocationType, Manufacturer
|
|
5
|
+
from nautobot.dcim.tables.devices import DeviceModuleInterfaceTable, InterfaceTable
|
|
6
|
+
from nautobot.dcim.tables.devicetypes import InterfaceTemplateTable
|
|
7
|
+
from nautobot.extras.models import Role, Status
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InterfaceTableRenderMixin:
|
|
11
|
+
"""Mixin for testing render_speed methods on interface tables."""
|
|
12
|
+
|
|
13
|
+
table_class = None
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def setUpTestData(cls):
|
|
17
|
+
manufacturer = Manufacturer.objects.create(name="Test Manufacturer")
|
|
18
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="Test Device Type")
|
|
19
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
20
|
+
location_type = LocationType.objects.get(name="Campus")
|
|
21
|
+
location = Location.objects.filter(location_type=location_type).first()
|
|
22
|
+
device_status = Status.objects.get_for_model(Device).first()
|
|
23
|
+
cls.interface_status = Status.objects.get_for_model(Interface).first()
|
|
24
|
+
|
|
25
|
+
cls.device = Device.objects.create(
|
|
26
|
+
name="Test Device",
|
|
27
|
+
device_type=device_type,
|
|
28
|
+
role=device_role,
|
|
29
|
+
location=location,
|
|
30
|
+
status=device_status,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def test_render_speed_duplex_with_value(self):
|
|
34
|
+
"""Test that the table renders humanized speed values."""
|
|
35
|
+
interface = Interface.objects.create(
|
|
36
|
+
device=self.device,
|
|
37
|
+
name="eth0",
|
|
38
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
39
|
+
status=self.interface_status,
|
|
40
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
41
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
queryset = Interface.objects.filter(pk=interface.pk)
|
|
45
|
+
table = self.table_class(queryset) # pylint: disable=not-callable
|
|
46
|
+
bound_row = table.rows[0]
|
|
47
|
+
rendered_speed = bound_row.get_cell("speed")
|
|
48
|
+
rendered_duplex = bound_row.get_cell("duplex")
|
|
49
|
+
|
|
50
|
+
self.assertEqual(rendered_speed, "1 Gbps")
|
|
51
|
+
self.assertEqual(rendered_duplex, "Full")
|
|
52
|
+
|
|
53
|
+
def test_render_speed_duplex_with_none(self):
|
|
54
|
+
"""Test that the table handles None speed value and renders an emdash."""
|
|
55
|
+
emdash = "\u2014"
|
|
56
|
+
interface = Interface.objects.create(
|
|
57
|
+
device=self.device,
|
|
58
|
+
name="eth1",
|
|
59
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
60
|
+
status=self.interface_status,
|
|
61
|
+
speed=None,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
queryset = Interface.objects.filter(pk=interface.pk)
|
|
65
|
+
table = self.table_class(queryset) # pylint: disable=not-callable
|
|
66
|
+
bound_row = table.rows[0]
|
|
67
|
+
rendered_speed = bound_row.get_cell("speed")
|
|
68
|
+
rendered_duplex = bound_row.get_cell("duplex")
|
|
69
|
+
|
|
70
|
+
self.assertEqual(rendered_speed, emdash)
|
|
71
|
+
self.assertEqual(rendered_duplex, emdash)
|
|
72
|
+
|
|
73
|
+
def test_render_speed_various(self):
|
|
74
|
+
"""Test that the table correctly humanizes various speed values."""
|
|
75
|
+
# Test all speed choices defined in InterfaceSpeedChoices
|
|
76
|
+
for speed_value, expected_output in InterfaceSpeedChoices.CHOICES:
|
|
77
|
+
with self.subTest(speed_value=speed_value, expected=expected_output):
|
|
78
|
+
interface = Interface.objects.create(
|
|
79
|
+
device=self.device,
|
|
80
|
+
name=f"eth-{speed_value}",
|
|
81
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
82
|
+
status=self.interface_status,
|
|
83
|
+
speed=speed_value,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
queryset = Interface.objects.filter(pk=interface.pk)
|
|
87
|
+
table = self.table_class(queryset) # pylint: disable=not-callable
|
|
88
|
+
bound_row = table.rows[0]
|
|
89
|
+
rendered_speed = bound_row.get_cell("speed")
|
|
90
|
+
|
|
91
|
+
self.assertEqual(rendered_speed, expected_output)
|
|
92
|
+
|
|
93
|
+
def test_render_duplex_various(self):
|
|
94
|
+
"""Test that the table correctly renders various duplex values."""
|
|
95
|
+
for duplex_value, expected_output in InterfaceDuplexChoices.CHOICES:
|
|
96
|
+
with self.subTest(duplex_value=duplex_value, expected=expected_output):
|
|
97
|
+
interface = Interface.objects.create(
|
|
98
|
+
device=self.device,
|
|
99
|
+
name=f"eth-{duplex_value}",
|
|
100
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
101
|
+
status=self.interface_status,
|
|
102
|
+
duplex=duplex_value,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
queryset = Interface.objects.filter(pk=interface.pk)
|
|
106
|
+
table = self.table_class(queryset) # pylint: disable=not-callable
|
|
107
|
+
bound_row = table.rows[0]
|
|
108
|
+
rendered_duplex = bound_row.get_cell("duplex")
|
|
109
|
+
|
|
110
|
+
self.assertEqual(rendered_duplex, expected_output)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class InterfaceTableTestCase(InterfaceTableRenderMixin, TestCase):
|
|
114
|
+
"""Test cases for InterfaceTable."""
|
|
115
|
+
|
|
116
|
+
table_class = InterfaceTable
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DeviceModuleInterfaceTableTestCase(InterfaceTableRenderMixin, TestCase):
|
|
120
|
+
"""Test cases for DeviceModuleInterfaceTable."""
|
|
121
|
+
|
|
122
|
+
table_class = DeviceModuleInterfaceTable
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class InterfaceTemplateTableTestCase(TestCase):
|
|
126
|
+
"""Render tests for InterfaceTemplateTable speed/duplex columns."""
|
|
127
|
+
|
|
128
|
+
@classmethod
|
|
129
|
+
def setUpTestData(cls):
|
|
130
|
+
manufacturer = Manufacturer.objects.create(name="Test Manuf Tmpl")
|
|
131
|
+
cls.device_type = DeviceType.objects.create(manufacturer=manufacturer, model="DT-Tmpl")
|
|
132
|
+
|
|
133
|
+
def test_render_speed_duplex_with_value(self):
|
|
134
|
+
interface_template = InterfaceTemplate.objects.create(
|
|
135
|
+
device_type=self.device_type,
|
|
136
|
+
name="tmpl-eth0",
|
|
137
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
138
|
+
speed=InterfaceSpeedChoices.SPEED_1G,
|
|
139
|
+
duplex=InterfaceDuplexChoices.DUPLEX_FULL,
|
|
140
|
+
)
|
|
141
|
+
table = InterfaceTemplateTable(InterfaceTemplate.objects.filter(pk=interface_template.pk))
|
|
142
|
+
bound_row = table.rows[0]
|
|
143
|
+
rendered_speed = bound_row.get_cell("speed") # pylint: disable=no-member
|
|
144
|
+
rendered_duplex = bound_row.get_cell("duplex") # pylint: disable=no-member
|
|
145
|
+
self.assertEqual(rendered_speed, "1 Gbps")
|
|
146
|
+
self.assertEqual(rendered_duplex, "Full")
|
|
147
|
+
|
|
148
|
+
def test_render_speed_duplex_with_none(self):
|
|
149
|
+
emdash = "\u2014"
|
|
150
|
+
interface_template = InterfaceTemplate.objects.create(
|
|
151
|
+
device_type=self.device_type,
|
|
152
|
+
name="tmpl-eth1",
|
|
153
|
+
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
154
|
+
)
|
|
155
|
+
table = InterfaceTemplateTable(InterfaceTemplate.objects.filter(pk=interface_template.pk))
|
|
156
|
+
bound_row = table.rows[0]
|
|
157
|
+
rendered_speed = bound_row.get_cell("speed") # pylint: disable=no-member
|
|
158
|
+
rendered_duplex = bound_row.get_cell("duplex") # pylint: disable=no-member
|
|
159
|
+
self.assertEqual(rendered_speed, emdash)
|
|
160
|
+
self.assertEqual(rendered_duplex, emdash)
|
|
@@ -6,7 +6,7 @@ import zoneinfo
|
|
|
6
6
|
from django.contrib.auth import get_user_model
|
|
7
7
|
from django.contrib.contenttypes.models import ContentType
|
|
8
8
|
from django.db.models import Q
|
|
9
|
-
from django.test import override_settings
|
|
9
|
+
from django.test import override_settings
|
|
10
10
|
from django.urls import reverse
|
|
11
11
|
from django.utils.html import strip_spaces_between_tags
|
|
12
12
|
from netaddr import EUI
|
|
@@ -30,6 +30,7 @@ from nautobot.dcim.choices import (
|
|
|
30
30
|
ConsolePortTypeChoices,
|
|
31
31
|
DeviceFaceChoices,
|
|
32
32
|
DeviceRedundancyGroupFailoverStrategyChoices,
|
|
33
|
+
InterfaceDuplexChoices,
|
|
33
34
|
InterfaceModeChoices,
|
|
34
35
|
InterfaceRedundancyGroupProtocolChoices,
|
|
35
36
|
InterfaceTypeChoices,
|
|
@@ -1293,7 +1294,6 @@ class ModuleTypeTestCase(
|
|
|
1293
1294
|
"comments": "changed comment",
|
|
1294
1295
|
}
|
|
1295
1296
|
|
|
1296
|
-
@tag("fix_in_v3")
|
|
1297
1297
|
def test_list_has_correct_links(self):
|
|
1298
1298
|
"""Assert that the ModuleType list view has import/export buttons for both CSV and YAML/JSON formats."""
|
|
1299
1299
|
self.add_permissions("dcim.add_moduletype", "dcim.view_moduletype")
|
|
@@ -1305,11 +1305,11 @@ class ModuleTypeTestCase(
|
|
|
1305
1305
|
csv_import_url = job_import_url(ContentType.objects.get_for_model(ModuleType))
|
|
1306
1306
|
# Dropdown provides both YAML/JSON and CSV import as options
|
|
1307
1307
|
self.assertInHTML(
|
|
1308
|
-
f'<a href="{yaml_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
|
|
1308
|
+
f'<a class="dropdown-item" href="{yaml_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
|
|
1309
1309
|
content,
|
|
1310
1310
|
)
|
|
1311
1311
|
self.assertInHTML(
|
|
1312
|
-
f'<a href="{csv_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
|
|
1312
|
+
f'<a class="dropdown-item" href="{csv_import_url}"><span class="mdi mdi-database-import text-secondary" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
|
|
1313
1313
|
content,
|
|
1314
1314
|
)
|
|
1315
1315
|
|
|
@@ -1322,11 +1322,11 @@ class ModuleTypeTestCase(
|
|
|
1322
1322
|
)
|
|
1323
1323
|
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
1324
1324
|
self.assertInHTML(
|
|
1325
|
-
'<button type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as YAML</button>',
|
|
1325
|
+
'<button class="dropdown-item" type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as YAML</button>',
|
|
1326
1326
|
content,
|
|
1327
1327
|
)
|
|
1328
1328
|
self.assertInHTML(
|
|
1329
|
-
'<button type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as CSV</button>',
|
|
1329
|
+
'<button class="dropdown-item" type="submit"><span class="mdi mdi-database-export text-secondary" aria-hidden="true"></span> Export as CSV</button>',
|
|
1330
1330
|
content,
|
|
1331
1331
|
)
|
|
1332
1332
|
|
|
@@ -1838,6 +1838,68 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1838
1838
|
"description": "new test description",
|
|
1839
1839
|
}
|
|
1840
1840
|
|
|
1841
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1842
|
+
def test_create_base_t_with_speed_and_duplex(self):
|
|
1843
|
+
self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
|
|
1844
|
+
url = reverse("dcim:interfacetemplate_add")
|
|
1845
|
+
dt = DeviceType.objects.first()
|
|
1846
|
+
data = {
|
|
1847
|
+
"device_type": dt.pk,
|
|
1848
|
+
"name_pattern": "Eth-View-1",
|
|
1849
|
+
"type": InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
1850
|
+
"mgmt_only": False,
|
|
1851
|
+
"speed": 1_000_000,
|
|
1852
|
+
"duplex": InterfaceDuplexChoices.DUPLEX_FULL,
|
|
1853
|
+
"_create": True,
|
|
1854
|
+
}
|
|
1855
|
+
response = self.client.post(url, data)
|
|
1856
|
+
|
|
1857
|
+
# Successful create redirects (JobResult or object list)
|
|
1858
|
+
self.assertIn(response.status_code, (302, 303))
|
|
1859
|
+
interface_template = InterfaceTemplate.objects.get(name="Eth-View-1")
|
|
1860
|
+
self.assertEqual(interface_template.speed, 1_000_000)
|
|
1861
|
+
self.assertEqual(interface_template.duplex, InterfaceDuplexChoices.DUPLEX_FULL)
|
|
1862
|
+
|
|
1863
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1864
|
+
def test_create_sfp_with_duplex_rejected(self):
|
|
1865
|
+
self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
|
|
1866
|
+
url = reverse("dcim:interfacetemplate_add")
|
|
1867
|
+
dt = DeviceType.objects.first()
|
|
1868
|
+
data = {
|
|
1869
|
+
"device_type": dt.pk,
|
|
1870
|
+
"name_pattern": "SFP-View-1",
|
|
1871
|
+
"type": InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
1872
|
+
"duplex": InterfaceDuplexChoices.DUPLEX_FULL,
|
|
1873
|
+
"_create": True,
|
|
1874
|
+
}
|
|
1875
|
+
response = self.client.post(url, data)
|
|
1876
|
+
# Form error returns 200 with field error displayed
|
|
1877
|
+
self.assertEqual(response.status_code, 200)
|
|
1878
|
+
content = response.content.decode(response.charset)
|
|
1879
|
+
self.assertIn("Duplex is only applicable to copper twisted-pair interfaces.", content)
|
|
1880
|
+
|
|
1881
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1882
|
+
def test_bulk_create_with_speed_and_duplex(self):
|
|
1883
|
+
self.add_permissions("dcim.add_interfacetemplate", "dcim.view_devicetype")
|
|
1884
|
+
url = reverse("dcim:interfacetemplate_add")
|
|
1885
|
+
dt = DeviceType.objects.first()
|
|
1886
|
+
data = {
|
|
1887
|
+
"device_type": dt.pk,
|
|
1888
|
+
"name_pattern": "Et[1-2]",
|
|
1889
|
+
"type": InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
1890
|
+
"mgmt_only": False,
|
|
1891
|
+
"speed": 1_000_000,
|
|
1892
|
+
"duplex": InterfaceDuplexChoices.DUPLEX_FULL,
|
|
1893
|
+
"_apply": True,
|
|
1894
|
+
}
|
|
1895
|
+
response = self.client.post(url, data)
|
|
1896
|
+
self.assertIn(response.status_code, (302, 303))
|
|
1897
|
+
objs = InterfaceTemplate.objects.filter(name__in=["Et1", "Et2"]).order_by("name")
|
|
1898
|
+
self.assertEqual(objs.count(), 2)
|
|
1899
|
+
for obj in objs:
|
|
1900
|
+
self.assertEqual(obj.speed, 1_000_000)
|
|
1901
|
+
self.assertEqual(obj.duplex, InterfaceDuplexChoices.DUPLEX_FULL)
|
|
1902
|
+
|
|
1841
1903
|
|
|
1842
1904
|
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1843
1905
|
model = FrontPortTemplate
|
|
@@ -4822,7 +4884,7 @@ class ControllerTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
4822
4884
|
model = Controller
|
|
4823
4885
|
filterset = ControllerFilterSet
|
|
4824
4886
|
custom_action_required_permissions = {
|
|
4825
|
-
"dcim:
|
|
4887
|
+
"dcim:controller_wireless_networks": [
|
|
4826
4888
|
"dcim.view_controller",
|
|
4827
4889
|
"wireless.view_controllermanageddevicegroupwirelessnetworkassignment",
|
|
4828
4890
|
],
|