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
|
@@ -35,7 +35,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
35
35
|
item = ViewNameBreadcrumbItem(view_name="home", label="Home")
|
|
36
36
|
context = Context({})
|
|
37
37
|
|
|
38
|
-
url, label = item.as_pair(context)
|
|
38
|
+
url, label = next(item.as_pair(context))
|
|
39
39
|
|
|
40
40
|
self.assertEqual(url, "/")
|
|
41
41
|
self.assertEqual(label, "Home")
|
|
@@ -50,7 +50,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
50
50
|
)
|
|
51
51
|
context = Context({})
|
|
52
52
|
|
|
53
|
-
url, label = item.as_pair(context)
|
|
53
|
+
url, label = next(item.as_pair(context))
|
|
54
54
|
|
|
55
55
|
self.assertEqual(url, f"/dcim/location-types/{self.location_type.pk}/?name=test")
|
|
56
56
|
self.assertEqual(label, "Filtered Locations Types")
|
|
@@ -65,7 +65,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
65
65
|
)
|
|
66
66
|
context = Context({"object": self.location_type})
|
|
67
67
|
|
|
68
|
-
url, label = item.as_pair(context)
|
|
68
|
+
url, label = next(item.as_pair(context))
|
|
69
69
|
|
|
70
70
|
self.assertEqual(
|
|
71
71
|
url, f"/dcim/location-types/{self.location_type.pk}/?{urlencode({'name': self.location_type.name})}"
|
|
@@ -79,7 +79,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
79
79
|
label=lambda context: f"Hi, {context['user']}!",
|
|
80
80
|
)
|
|
81
81
|
context = Context({"user": "Frodo"})
|
|
82
|
-
url, label = item.as_pair(context)
|
|
82
|
+
url, label = next(item.as_pair(context))
|
|
83
83
|
self.assertEqual(url, "/")
|
|
84
84
|
self.assertEqual(label, "Hi, Frodo!")
|
|
85
85
|
|
|
@@ -148,7 +148,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
148
148
|
item = ModelBreadcrumbItem(**test_case["kwargs"])
|
|
149
149
|
context = Context({"object": self.location_type, "model_type": Device, "device_name": "abc"})
|
|
150
150
|
|
|
151
|
-
url, label = item.as_pair(context)
|
|
151
|
+
url, label = next(item.as_pair(context))
|
|
152
152
|
|
|
153
153
|
self.assertEqual(url, test_case["expected_url"])
|
|
154
154
|
self.assertEqual(label, test_case["expected_label"])
|
|
@@ -157,7 +157,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
157
157
|
item = ModelBreadcrumbItem(model_key="object", label="custom LaBeL")
|
|
158
158
|
context = Context({"object": self.location_type})
|
|
159
159
|
|
|
160
|
-
_, label = item.as_pair(context)
|
|
160
|
+
_, label = next(item.as_pair(context))
|
|
161
161
|
self.assertEqual(label, "custom LaBeL")
|
|
162
162
|
|
|
163
163
|
def test_model_item_from_context(self):
|
|
@@ -165,7 +165,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
165
165
|
item = ModelBreadcrumbItem(model_key="object")
|
|
166
166
|
context = Context({"object": self.location_type})
|
|
167
167
|
|
|
168
|
-
url, label = item.as_pair(context)
|
|
168
|
+
url, label = next(item.as_pair(context))
|
|
169
169
|
|
|
170
170
|
self.assertEqual(url, "/dcim/location-types/")
|
|
171
171
|
self.assertEqual(label, "Location Types")
|
|
@@ -175,7 +175,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
175
175
|
item = InstanceBreadcrumbItem(instance_key="object")
|
|
176
176
|
context = Context({"object": self.location_type})
|
|
177
177
|
|
|
178
|
-
url, label = item.as_pair(context)
|
|
178
|
+
url, label = next(item.as_pair(context))
|
|
179
179
|
|
|
180
180
|
self.assertEqual(url, f"/dcim/location-types/{self.location_type.pk}/")
|
|
181
181
|
self.assertEqual(label, str(self.location_type))
|
|
@@ -192,7 +192,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
192
192
|
|
|
193
193
|
for item in items:
|
|
194
194
|
with self.subTest():
|
|
195
|
-
_, label = item.as_pair(context)
|
|
195
|
+
_, label = next(item.as_pair(context))
|
|
196
196
|
self.assertEqual(label, "Custom Label")
|
|
197
197
|
|
|
198
198
|
def test_no_reverse_match(self):
|
|
@@ -200,7 +200,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
200
200
|
item = ViewNameBreadcrumbItem(view_name="nonexistent")
|
|
201
201
|
context = Context({})
|
|
202
202
|
|
|
203
|
-
url, label = item.as_pair(context)
|
|
203
|
+
url, label = next(item.as_pair(context))
|
|
204
204
|
|
|
205
205
|
self.assertEqual(url, "")
|
|
206
206
|
self.assertEqual(label, "")
|
|
@@ -210,12 +210,12 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
210
210
|
context = Context({})
|
|
211
211
|
item = InstanceBreadcrumbItem(instance_key="missing_key")
|
|
212
212
|
|
|
213
|
-
url, label = item.as_pair(context)
|
|
213
|
+
url, label = next(item.as_pair(context))
|
|
214
214
|
self.assertEqual(url, "")
|
|
215
215
|
self.assertEqual(label, "")
|
|
216
216
|
|
|
217
217
|
item = ModelBreadcrumbItem(model_key="missing_key")
|
|
218
|
-
url, label = item.as_pair(context)
|
|
218
|
+
url, label = next(item.as_pair(context))
|
|
219
219
|
self.assertEqual(url, "")
|
|
220
220
|
self.assertEqual(label, "")
|
|
221
221
|
|
|
@@ -223,7 +223,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
223
223
|
context = Context({"object": LocationType.objects.create(name="custom name")})
|
|
224
224
|
item = InstanceBreadcrumbItem()
|
|
225
225
|
|
|
226
|
-
_, label = item.as_pair(context)
|
|
226
|
+
_, label = next(item.as_pair(context))
|
|
227
227
|
self.assertEqual(label, "custom name")
|
|
228
228
|
|
|
229
229
|
def test_instance_item_is_working_with_directly_passed_instance(self):
|
|
@@ -231,7 +231,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
231
231
|
instance = LocationType.objects.create(name="cUsToM CoUnTrY")
|
|
232
232
|
item = InstanceBreadcrumbItem(instance=instance)
|
|
233
233
|
|
|
234
|
-
url, label = item.as_pair(context)
|
|
234
|
+
url, label = next(item.as_pair(context))
|
|
235
235
|
self.assertEqual(url, f"/dcim/location-types/{instance.pk}/")
|
|
236
236
|
self.assertEqual(label, "cUsToM CoUnTrY")
|
|
237
237
|
|
|
@@ -244,17 +244,17 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
244
244
|
context = Context({"object": location, "status": status})
|
|
245
245
|
|
|
246
246
|
item = InstanceBreadcrumbItem(instance=context_object_attr("location_type"))
|
|
247
|
-
url, label = item.as_pair(context)
|
|
247
|
+
url, label = next(item.as_pair(context))
|
|
248
248
|
self.assertEqual(url, f"/dcim/location-types/{location_type.pk}/")
|
|
249
|
-
self.assertEqual(label, "
|
|
249
|
+
self.assertEqual(label, "State")
|
|
250
250
|
|
|
251
251
|
item = BaseBreadcrumbItem(label=context_object_attr("name", context_key="status"))
|
|
252
|
-
url, label = item.as_pair(context)
|
|
252
|
+
url, label = next(item.as_pair(context))
|
|
253
253
|
self.assertEqual(url, "")
|
|
254
254
|
self.assertEqual(label, status.name)
|
|
255
255
|
|
|
256
256
|
item = InstanceBreadcrumbItem(instance=context_object_attr("location_type.parent"))
|
|
257
|
-
url, label = item.as_pair(context)
|
|
257
|
+
url, label = next(item.as_pair(context))
|
|
258
258
|
self.assertEqual(url, f"/dcim/location-types/{parent_location_type.pk}/")
|
|
259
259
|
self.assertEqual(label, "Country")
|
|
260
260
|
|
|
@@ -263,7 +263,7 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
263
263
|
context = Context({"object": LocationType.objects.create(name="custom name", parent=parent_location_type)})
|
|
264
264
|
item = InstanceParentBreadcrumbItem()
|
|
265
265
|
|
|
266
|
-
_, label = item.as_pair(context)
|
|
266
|
+
_, label = next(item.as_pair(context))
|
|
267
267
|
self.assertEqual(label, "cUsToM CoUnTrY")
|
|
268
268
|
|
|
269
269
|
def test_instance_parent_is_create_proper_url(self):
|
|
@@ -273,12 +273,12 @@ class BreadcrumbItemsTestCase(TestCase):
|
|
|
273
273
|
context = Context({"object": location_type})
|
|
274
274
|
|
|
275
275
|
item = InstanceParentBreadcrumbItem()
|
|
276
|
-
url, label = item.as_pair(context)
|
|
276
|
+
url, label = next(item.as_pair(context))
|
|
277
277
|
self.assertEqual(url, f"/dcim/location-types/?parent={parent_location_type.pk}")
|
|
278
278
|
self.assertEqual(label, "Country")
|
|
279
279
|
|
|
280
280
|
item = InstanceParentBreadcrumbItem(parent_query_param="location", parent_lookup_key="name")
|
|
281
|
-
url, label = item.as_pair(context)
|
|
281
|
+
url, label = next(item.as_pair(context))
|
|
282
282
|
self.assertEqual(url, "/dcim/location-types/?location=Country")
|
|
283
283
|
self.assertEqual(label, "Country")
|
|
284
284
|
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -16,7 +16,7 @@ from nautobot.core.jobs import ExportObjectList
|
|
|
16
16
|
from nautobot.core.jobs.cleanup import CleanupTypes
|
|
17
17
|
from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
|
|
18
18
|
from nautobot.core.testing.context import load_event_broker_override_settings
|
|
19
|
-
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
19
|
+
from nautobot.dcim.models import Device, DeviceType, FrontPortTemplate, Location, LocationType, Manufacturer
|
|
20
20
|
from nautobot.extras.choices import DynamicGroupTypeChoices, JobResultStatusChoices, LogLevelChoices
|
|
21
21
|
from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
|
|
22
22
|
from nautobot.extras.models import (
|
|
@@ -35,7 +35,7 @@ from nautobot.extras.models import (
|
|
|
35
35
|
)
|
|
36
36
|
from nautobot.extras.models.metadata import ObjectMetadata
|
|
37
37
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix
|
|
38
|
-
from nautobot.users.models import ObjectPermission
|
|
38
|
+
from nautobot.users.models import ObjectPermission, User
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class ExportObjectListTest(TransactionTestCase):
|
|
@@ -1252,10 +1252,8 @@ class BulkDeleteTestCase(TransactionTestCase):
|
|
|
1252
1252
|
|
|
1253
1253
|
|
|
1254
1254
|
class RefreshDynamicGroupCacheJobButtonReceiverTestCase(TransactionTestCase):
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
self.job_module = "nautobot.core.jobs.groups"
|
|
1258
|
-
self.job_name = "RefreshDynamicGroupCacheJobButtonReceiver"
|
|
1255
|
+
job_module = "nautobot.core.jobs.groups"
|
|
1256
|
+
job_name = "RefreshDynamicGroupCacheJobButtonReceiver"
|
|
1259
1257
|
|
|
1260
1258
|
def test_successful_cache_refresh(self):
|
|
1261
1259
|
LocationType.objects.create(name="DG Test LT 1")
|
|
@@ -1322,3 +1320,72 @@ class RefreshDynamicGroupCacheJobButtonReceiverTestCase(TransactionTestCase):
|
|
|
1322
1320
|
log_fail.message,
|
|
1323
1321
|
"The members of this Dynamic Group are statically defined and do not need to be recalculated.",
|
|
1324
1322
|
)
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
class ValidateModelDataTestCase(TransactionTestCase):
|
|
1326
|
+
job_module = "nautobot.core.jobs"
|
|
1327
|
+
job_name = "ValidateModelData"
|
|
1328
|
+
|
|
1329
|
+
def test_successful_validation(self):
|
|
1330
|
+
job_result = create_job_result_and_run_job(
|
|
1331
|
+
self.job_module,
|
|
1332
|
+
self.job_name,
|
|
1333
|
+
content_types=[ContentType.objects.get_for_model(Status).pk],
|
|
1334
|
+
verbose=True,
|
|
1335
|
+
)
|
|
1336
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1337
|
+
|
|
1338
|
+
def test_failure_on_invalid_dg(self):
|
|
1339
|
+
dg = DynamicGroup(
|
|
1340
|
+
name="Legacy rear_port_template filter",
|
|
1341
|
+
filter={"rear_port_template": "74aac78c-fabb-468c-a036-26c46c56f27a"},
|
|
1342
|
+
content_type=ContentType.objects.get_for_model(FrontPortTemplate),
|
|
1343
|
+
)
|
|
1344
|
+
dg.save(update_cached_members=False)
|
|
1345
|
+
job_result = create_job_result_and_run_job(
|
|
1346
|
+
self.job_module,
|
|
1347
|
+
self.job_name,
|
|
1348
|
+
content_types=[ContentType.objects.get_for_model(DynamicGroup).pk],
|
|
1349
|
+
verbose=False,
|
|
1350
|
+
)
|
|
1351
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1352
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1353
|
+
self.assertIn("Enter a list of values", log_fail.message)
|
|
1354
|
+
|
|
1355
|
+
def test_warning_without_permission(self):
|
|
1356
|
+
job_result = create_job_result_and_run_job(
|
|
1357
|
+
self.job_module,
|
|
1358
|
+
self.job_name,
|
|
1359
|
+
username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
|
|
1360
|
+
content_types=[ContentType.objects.get_for_model(Status).pk],
|
|
1361
|
+
verbose=True,
|
|
1362
|
+
)
|
|
1363
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1364
|
+
log_warn = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING)
|
|
1365
|
+
self.assertEqual("No statuses found", log_warn.message)
|
|
1366
|
+
self.assertIsNone(
|
|
1367
|
+
JobLogEntry.objects.filter(
|
|
1368
|
+
job_result=job_result, log_level=LogLevelChoices.LOG_SUCCESS, message="Validated successfully"
|
|
1369
|
+
).first()
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
def test_no_restrict_superuser(self):
|
|
1373
|
+
job_result = create_job_result_and_run_job(
|
|
1374
|
+
self.job_module,
|
|
1375
|
+
self.job_name,
|
|
1376
|
+
content_types=[ContentType.objects.get_for_model(User).pk],
|
|
1377
|
+
verbose=True,
|
|
1378
|
+
)
|
|
1379
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1380
|
+
|
|
1381
|
+
def test_no_restrict_non_superuser(self):
|
|
1382
|
+
job_result = create_job_result_and_run_job(
|
|
1383
|
+
self.job_module,
|
|
1384
|
+
self.job_name,
|
|
1385
|
+
username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
|
|
1386
|
+
content_types=[ContentType.objects.get_for_model(User).pk],
|
|
1387
|
+
verbose=True,
|
|
1388
|
+
)
|
|
1389
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1390
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1391
|
+
self.assertIn("Unable to apply access permissions to users.user", log_fail.message)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
from django.test import override_settings, RequestFactory
|
|
4
|
+
from django.urls import reverse
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
|
|
7
|
+
from nautobot.cloud.views import CloudResourceTypeUIViewSet
|
|
8
|
+
from nautobot.core.testing import TestCase
|
|
9
|
+
from nautobot.core.ui.titles import Titles
|
|
10
|
+
from nautobot.core.views.renderers import NautobotHTMLRenderer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ObjectListViewTitlesTest(TestCase):
|
|
14
|
+
"""
|
|
15
|
+
Test suite for verifying that Titles are correctly set in ObjectListView rendering.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
user_permissions = ["cloud.view_cloudresourcetype"]
|
|
19
|
+
|
|
20
|
+
def setUp(self):
|
|
21
|
+
super().setUp()
|
|
22
|
+
self.factory = RequestFactory()
|
|
23
|
+
|
|
24
|
+
@override_settings(ALLOWED_HOSTS=["*"], PAGINATE_COUNT=5, MAX_PAGE_SIZE=10)
|
|
25
|
+
def test_uiviewset_list_view_title(self):
|
|
26
|
+
"""
|
|
27
|
+
Test that the list view title is correctly set from the Titles configuration.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
path = reverse("cloud:cloudresourcetype_list")
|
|
31
|
+
request = self.factory.get(path)
|
|
32
|
+
request.user = self.user
|
|
33
|
+
viewset_class = CloudResourceTypeUIViewSet
|
|
34
|
+
with patch.object(CloudResourceTypeUIViewSet, "view_titles", Titles(titles={"list": "Burritos"})):
|
|
35
|
+
view = viewset_class()
|
|
36
|
+
view.action_map = {"get": "list"}
|
|
37
|
+
|
|
38
|
+
request = view.initialize_request(request)
|
|
39
|
+
|
|
40
|
+
view.setup(request)
|
|
41
|
+
view.initial(request)
|
|
42
|
+
|
|
43
|
+
renderer = NautobotHTMLRenderer()
|
|
44
|
+
context = renderer.get_context(
|
|
45
|
+
data={},
|
|
46
|
+
accepted_media_type="text/html",
|
|
47
|
+
renderer_context={"view": view, "request": request, "response": Response({})},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Verify that the title is set in the context
|
|
51
|
+
self.assertIn("view_titles", context)
|
|
52
|
+
self.assertNotIn(
|
|
53
|
+
"title", context
|
|
54
|
+
) # title is used within the render path but should not be directly in context
|
|
55
|
+
self.assertEqual(context["view_titles"], viewset_class.view_titles)
|
|
56
|
+
|
|
57
|
+
# Finally, render the view and verify the title appears in the response
|
|
58
|
+
response = self.client.get(path)
|
|
59
|
+
self.assertContains(response, "Burritos")
|
|
@@ -132,6 +132,7 @@ class SettingsJSONSchemaTestCase(TestCase):
|
|
|
132
132
|
"CSRF_FAILURE_VIEW",
|
|
133
133
|
"DATA_UPLOAD_MAX_NUMBER_FIELDS",
|
|
134
134
|
"DEFAULT_AUTO_FIELD",
|
|
135
|
+
"DJANGO_TABLES2_TEMPLATE",
|
|
135
136
|
"DRF_REACT_TEMPLATE_TYPE_MAP",
|
|
136
137
|
"EXEMPT_EXCLUDE_MODELS",
|
|
137
138
|
"FILTERS_NULL_CHOICE_LABEL",
|
|
@@ -63,6 +63,9 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
63
63
|
self.assertEqual(helpers.pre_tag(None), '<span class="text-secondary">—</span>')
|
|
64
64
|
self.assertEqual(helpers.pre_tag([]), "<pre>[]</pre>")
|
|
65
65
|
self.assertEqual(helpers.pre_tag("something"), "<pre>something</pre>")
|
|
66
|
+
self.assertEqual(helpers.pre_tag("", format_empty_value=False), '<span class="text-secondary">—</span>')
|
|
67
|
+
self.assertEqual(helpers.pre_tag([], format_empty_value=False), '<span class="text-secondary">—</span>')
|
|
68
|
+
self.assertEqual(helpers.pre_tag("something", format_empty_value=False), "<pre>something</pre>")
|
|
66
69
|
|
|
67
70
|
def test_add_html_id(self):
|
|
68
71
|
# Case where what we have isn't actually a HTML element but just a bare string
|
|
@@ -194,7 +197,13 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
194
197
|
def test_humanize_speed(self):
|
|
195
198
|
self.assertEqual(helpers.humanize_speed(1544), "1.544 Mbps")
|
|
196
199
|
self.assertEqual(helpers.humanize_speed(100000), "100 Mbps")
|
|
200
|
+
self.assertEqual(helpers.humanize_speed(2500000), "2.5 Gbps")
|
|
197
201
|
self.assertEqual(helpers.humanize_speed(10000000), "10 Gbps")
|
|
202
|
+
self.assertEqual(helpers.humanize_speed(100000000), "100 Gbps")
|
|
203
|
+
self.assertEqual(helpers.humanize_speed(1000000000), "1 Tbps")
|
|
204
|
+
self.assertEqual(helpers.humanize_speed(1600000000), "1.6 Tbps")
|
|
205
|
+
self.assertEqual(helpers.humanize_speed(10000000000), "10 Tbps")
|
|
206
|
+
self.assertEqual(helpers.humanize_speed(100000000000), "100 Tbps")
|
|
198
207
|
|
|
199
208
|
def test_tzoffset(self):
|
|
200
209
|
self.assertTrue(callable(helpers.tzoffset))
|
|
@@ -107,22 +107,6 @@ class TitlesTestCase(TestCase):
|
|
|
107
107
|
"context": {"view_action": "bulk_update", "objs_count": 10, "verbose_name_plural": "devices"},
|
|
108
108
|
"expected": "Editing 10 Devices",
|
|
109
109
|
},
|
|
110
|
-
{
|
|
111
|
-
"name": "changelog_action",
|
|
112
|
-
"context": {
|
|
113
|
-
"view_action": "changelog",
|
|
114
|
-
"object": location_type,
|
|
115
|
-
},
|
|
116
|
-
"expected": "Test Location Type Title - Change Log",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"name": "notes_action",
|
|
120
|
-
"context": {
|
|
121
|
-
"view_action": "notes",
|
|
122
|
-
"object": location_type,
|
|
123
|
-
},
|
|
124
|
-
"expected": "Test Location Type Title - Notes",
|
|
125
|
-
},
|
|
126
110
|
{
|
|
127
111
|
"name": "approve_action",
|
|
128
112
|
"context": {"view_action": "approve", "verbose_name": "device"},
|
nautobot/core/tests/test_ui.py
CHANGED
|
@@ -32,7 +32,9 @@ from nautobot.core.ui.object_detail import (
|
|
|
32
32
|
SectionChoices,
|
|
33
33
|
)
|
|
34
34
|
from nautobot.dcim.models import Device, DeviceRedundancyGroup, Location
|
|
35
|
+
from nautobot.dcim.tables import DeviceModuleInterfaceTable
|
|
35
36
|
from nautobot.dcim.tables.devices import DeviceTable
|
|
37
|
+
from nautobot.dcim.views import DeviceUIViewSet
|
|
36
38
|
from nautobot.ipam.models import Prefix
|
|
37
39
|
|
|
38
40
|
|
|
@@ -355,9 +357,80 @@ class EChartsBaseTests(TestCase):
|
|
|
355
357
|
self.assertEqual(config["series"][0]["name"], "S1")
|
|
356
358
|
self.assertEqual(config["series"][1]["name"], "S2")
|
|
357
359
|
|
|
358
|
-
def
|
|
359
|
-
|
|
360
|
-
|
|
360
|
+
def test_get_config_combined_charts_with_complex_data(self):
|
|
361
|
+
chart2 = EChartsBase(
|
|
362
|
+
chart_type=EChartsTypeChoices.LINE,
|
|
363
|
+
data={
|
|
364
|
+
"Compliant": {"aaa1": 5, "dns1": 12, "ntp1": 8},
|
|
365
|
+
"Non Compliant": {"aaa1": 10, "dns1": 20, "ntp1": 15},
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
chart1 = EChartsBase(
|
|
369
|
+
chart_type=EChartsTypeChoices.BAR,
|
|
370
|
+
data={
|
|
371
|
+
"Compliant": {"aaa": 5, "dns": 12, "ntp": 8},
|
|
372
|
+
"Non Compliant": {"aaa": 10, "dns": 20, "ntp": 15},
|
|
373
|
+
},
|
|
374
|
+
combined_with=chart2,
|
|
375
|
+
)
|
|
376
|
+
config = chart1.get_config()
|
|
377
|
+
self.assertEqual(len(config["series"]), 4)
|
|
378
|
+
|
|
379
|
+
self.assertEqual(config["series"][0], {"name": "Compliant", "type": "bar", "data": [5, 12, 8]})
|
|
380
|
+
self.assertEqual(config["series"][1], {"name": "Non Compliant", "type": "bar", "data": [10, 20, 15]})
|
|
381
|
+
self.assertEqual(
|
|
382
|
+
config["series"][2],
|
|
383
|
+
{"name": "Compliant", "type": "line", "data": [5, 12, 8], "smooth": False, "lineStyle": {}},
|
|
384
|
+
)
|
|
385
|
+
self.assertEqual(
|
|
386
|
+
config["series"][3],
|
|
387
|
+
{"name": "Non Compliant", "type": "line", "data": [10, 20, 15], "smooth": False, "lineStyle": {}},
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def test_get_config_with_context_callable_and_combined_chart(self):
|
|
391
|
+
def main_data(ctx):
|
|
392
|
+
return {
|
|
393
|
+
"Compliant": {"aaa1": ctx.get("aaa1_count", 0), "dns1": 13, "ntp1": 8},
|
|
394
|
+
"Non Compliant": {"aaa1": 10, "dns1": 20, "ntp1": 15},
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
def combined_data(ctx):
|
|
398
|
+
return {
|
|
399
|
+
"Compliant": {"aaa": 5, "dns": ctx.get("dns_count", 0), "ntp1": 8},
|
|
400
|
+
"Non Compliant": {"aaa": 10, "dns": 20, "ntp1": 15},
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
ctx = Context({"aaa1_count": 5, "dns_count": 12})
|
|
404
|
+
combined_chart = EChartsBase(chart_type=EChartsTypeChoices.LINE, data=combined_data)
|
|
405
|
+
main_chart = EChartsBase(
|
|
406
|
+
chart_type=EChartsTypeChoices.BAR,
|
|
407
|
+
data=main_data,
|
|
408
|
+
combined_with=combined_chart,
|
|
409
|
+
)
|
|
410
|
+
config = main_chart.get_config(context=ctx)
|
|
411
|
+
self.assertEqual(len(config["series"]), 4)
|
|
412
|
+
|
|
413
|
+
self.assertEqual(config["series"][0]["data"][0], 5) # aaa1_count
|
|
414
|
+
self.assertEqual(config["series"][2]["data"][1], 12) # dns_count
|
|
415
|
+
|
|
416
|
+
def test_get_config_with_context_callable(self):
|
|
417
|
+
def dynamic_data(ctx):
|
|
418
|
+
return {"data": {"Devices": ctx.get("device_count", 0)}}
|
|
419
|
+
|
|
420
|
+
ctx = Context({"device_count": 42})
|
|
421
|
+
chart = EChartsBase(chart_type=EChartsTypeChoices.PIE, data=dynamic_data)
|
|
422
|
+
config = chart.get_config(context=ctx)
|
|
423
|
+
# check that after getting config data doesn't change it's still dynami_data function
|
|
424
|
+
self.assertEqual(chart.data, dynamic_data)
|
|
425
|
+
self.assertEqual(config["series"][0]["data"][0]["value"], 42)
|
|
426
|
+
ctx = Context({"device_count": 45})
|
|
427
|
+
config = chart.get_config(context=ctx)
|
|
428
|
+
self.assertEqual(config["series"][0]["data"][0]["value"], 45)
|
|
429
|
+
|
|
430
|
+
def test_get_config_with_context_ignored_when_data_is_not_callable(self):
|
|
431
|
+
ctx = Context({"some": "value"})
|
|
432
|
+
chart = EChartsBase(data=self.data_normalized)
|
|
433
|
+
config = chart.get_config(context=ctx)
|
|
361
434
|
self.assertEqual(config["series"][0]["data"], [1, 2])
|
|
362
435
|
|
|
363
436
|
|
|
@@ -492,6 +565,52 @@ class ObjectDetailContentExtraTabsTest(TestCase):
|
|
|
492
565
|
self.default_tabs_id.append("services")
|
|
493
566
|
self.assertListEqual(tab_ids, self.default_tabs_id)
|
|
494
567
|
|
|
568
|
+
def test_tab_id_url_as_action(self):
|
|
569
|
+
"""
|
|
570
|
+
Test that when you create a panel with a tab_id that matches a viewset action,
|
|
571
|
+
the return_url is constructed correctly.
|
|
572
|
+
"""
|
|
573
|
+
self.add_permissions("dcim.add_interface", "dcim.change_interface")
|
|
574
|
+
device_info = Device.objects.first()
|
|
575
|
+
|
|
576
|
+
panel = DeviceUIViewSet.DeviceInterfacesTablePanel(
|
|
577
|
+
weight=100,
|
|
578
|
+
section=SectionChoices.FULL_WIDTH,
|
|
579
|
+
table_title="Interfaces",
|
|
580
|
+
table_class=DeviceModuleInterfaceTable,
|
|
581
|
+
table_attribute="vc_interfaces",
|
|
582
|
+
related_field_name="device",
|
|
583
|
+
tab_id="interfaces",
|
|
584
|
+
)
|
|
585
|
+
context = {"request": self.request, "object": device_info}
|
|
586
|
+
panel_context = panel.get_extra_context(context)
|
|
587
|
+
|
|
588
|
+
return_url = f"/dcim/devices/{device_info.pk}/interfaces/"
|
|
589
|
+
self.assertTrue(panel_context["body_content_table_add_url"].endswith(return_url))
|
|
590
|
+
|
|
591
|
+
def test_tab_id_url_as_param(self):
|
|
592
|
+
"""
|
|
593
|
+
Test that when you create a panel with a tab_id that does NOT matches a viewset action,
|
|
594
|
+
the return_url is constructed correctly.
|
|
595
|
+
"""
|
|
596
|
+
self.add_permissions("dcim.add_interface", "dcim.change_interface")
|
|
597
|
+
device_info = Device.objects.first()
|
|
598
|
+
|
|
599
|
+
panel = DeviceUIViewSet.DeviceInterfacesTablePanel(
|
|
600
|
+
weight=100,
|
|
601
|
+
section=SectionChoices.FULL_WIDTH,
|
|
602
|
+
table_title="Interfaces",
|
|
603
|
+
table_class=DeviceModuleInterfaceTable,
|
|
604
|
+
table_attribute="vc_interfaces",
|
|
605
|
+
related_field_name="device",
|
|
606
|
+
tab_id="interfaces-not-exist",
|
|
607
|
+
)
|
|
608
|
+
context = {"request": self.request, "object": device_info}
|
|
609
|
+
panel_context = panel.get_extra_context(context)
|
|
610
|
+
|
|
611
|
+
return_url = f"&return_url=/dcim/devices/{device_info.pk}/?tab=interfaces-not-exist"
|
|
612
|
+
self.assertTrue(panel_context["body_content_table_add_url"].endswith(return_url))
|
|
613
|
+
|
|
495
614
|
def test_extra_tab_panel_context(self):
|
|
496
615
|
"""
|
|
497
616
|
Confirming that extra tab panels produce the correct context,
|
|
@@ -22,7 +22,13 @@ from nautobot.core.utils.cache import construct_cache_key
|
|
|
22
22
|
from nautobot.core.utils.migrations import update_object_change_ct_for_replaced_models
|
|
23
23
|
from nautobot.core.utils.module_loading import check_name_safe_to_import_privately, import_string_optional
|
|
24
24
|
from nautobot.data_validation import models as data_validation_models
|
|
25
|
-
from nautobot.dcim import
|
|
25
|
+
from nautobot.dcim import (
|
|
26
|
+
filters as dcim_filters,
|
|
27
|
+
forms as dcim_forms,
|
|
28
|
+
models as dcim_models,
|
|
29
|
+
tables,
|
|
30
|
+
views as dcim_views,
|
|
31
|
+
)
|
|
26
32
|
from nautobot.extras import models as extras_models, utils as extras_utils
|
|
27
33
|
from nautobot.extras.choices import ObjectChangeActionChoices, RelationshipTypeChoices
|
|
28
34
|
from nautobot.extras.filters import StatusFilterSet
|
|
@@ -329,6 +335,26 @@ class GetFooForModelTest(TestCase):
|
|
|
329
335
|
instance = extras_models.GraphQLQuery.objects.create(name="FizzBuzz", query="{devices { name }}")
|
|
330
336
|
self.assertIsNone(lookup.get_user_from_instance(instance))
|
|
331
337
|
|
|
338
|
+
def test_get_breadcrumbs_for_model(self):
|
|
339
|
+
breadcrumbs = lookup.get_breadcrumbs_for_model(dcim_models.Device)
|
|
340
|
+
self.assertEqual(breadcrumbs.items, dcim_views.DeviceUIViewSet.get_breadcrumbs(dcim_models.Device).items)
|
|
341
|
+
breadcrumbs = lookup.get_breadcrumbs_for_model(dcim_models.Device, view_type="")
|
|
342
|
+
self.assertEqual(
|
|
343
|
+
breadcrumbs.items, dcim_views.DeviceUIViewSet.get_breadcrumbs(dcim_models.Device, view_type="").items
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def test_get_detail_view_components_context_for_model(self):
|
|
347
|
+
context = lookup.get_detail_view_components_context_for_model(dcim_models.Device)
|
|
348
|
+
self.assertEqual(
|
|
349
|
+
context["breadcrumbs"].items, lookup.get_breadcrumbs_for_model(dcim_models.Device, view_type="").items
|
|
350
|
+
)
|
|
351
|
+
self.assertEqual(
|
|
352
|
+
context["object_detail_content"], lookup.get_object_detail_content_for_model(dcim_models.Device)
|
|
353
|
+
)
|
|
354
|
+
self.assertEqual(
|
|
355
|
+
context["view_titles"].titles, lookup.get_view_titles_for_model(dcim_models.Device, view_type="").titles
|
|
356
|
+
)
|
|
357
|
+
|
|
332
358
|
def test_get_filterset_for_model(self):
|
|
333
359
|
"""
|
|
334
360
|
Test that `get_filterset_for_model` returns the right FilterSet for various inputs.
|
|
@@ -351,6 +377,12 @@ class GetFooForModelTest(TestCase):
|
|
|
351
377
|
self.assertEqual(lookup.get_form_for_model("dcim.location"), dcim_forms.LocationForm)
|
|
352
378
|
self.assertEqual(lookup.get_form_for_model(dcim_models.Location), dcim_forms.LocationForm)
|
|
353
379
|
|
|
380
|
+
def test_get_object_detail_content_for_model(self):
|
|
381
|
+
self.assertEqual(
|
|
382
|
+
lookup.get_object_detail_content_for_model(dcim_models.Device),
|
|
383
|
+
dcim_views.DeviceUIViewSet.object_detail_content,
|
|
384
|
+
)
|
|
385
|
+
|
|
354
386
|
def test_get_related_field_for_models(self):
|
|
355
387
|
"""
|
|
356
388
|
Test that `get_related_field_for_models` returns the appropriate field for various inputs.
|
|
@@ -463,6 +495,14 @@ class GetFooForModelTest(TestCase):
|
|
|
463
495
|
# Testing unconventional table name
|
|
464
496
|
self.assertEqual(lookup.get_table_class_string_from_view_name("ipam:prefix_list"), "PrefixDetailTable")
|
|
465
497
|
|
|
498
|
+
def test_get_view_titles_for_model(self):
|
|
499
|
+
view_titles = lookup.get_view_titles_for_model(dcim_models.Device)
|
|
500
|
+
self.assertEqual(view_titles.titles, dcim_views.DeviceUIViewSet.get_view_titles(dcim_models.Device).titles)
|
|
501
|
+
view_titles = lookup.get_view_titles_for_model(dcim_models.Device, view_type="")
|
|
502
|
+
self.assertEqual(
|
|
503
|
+
view_titles.titles, dcim_views.DeviceUIViewSet.get_view_titles(dcim_models.Device, view_type="").titles
|
|
504
|
+
)
|
|
505
|
+
|
|
466
506
|
|
|
467
507
|
class IsTaggableTest(TestCase):
|
|
468
508
|
def test_is_taggable_true(self):
|