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
|
@@ -4,7 +4,6 @@ import contextlib
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from enum import Enum
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Any
|
|
8
7
|
import uuid
|
|
9
8
|
|
|
10
9
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -43,7 +42,7 @@ from nautobot.core.templatetags.helpers import (
|
|
|
43
42
|
from nautobot.core.ui.choices import LayoutChoices, SectionChoices
|
|
44
43
|
from nautobot.core.ui.echarts import EChartsBase
|
|
45
44
|
from nautobot.core.ui.utils import render_component_template
|
|
46
|
-
from nautobot.core.utils.lookup import get_filterset_for_model, get_route_for_model
|
|
45
|
+
from nautobot.core.utils.lookup import get_filterset_for_model, get_route_for_model, get_view_for_model
|
|
47
46
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
48
47
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
49
48
|
from nautobot.core.views.utils import get_obj_from_context
|
|
@@ -247,6 +246,9 @@ class Button(Component):
|
|
|
247
246
|
"""
|
|
248
247
|
if self.link_name and self.link_includes_pk:
|
|
249
248
|
obj = get_obj_from_context(context, self.context_object_key)
|
|
249
|
+
if not obj:
|
|
250
|
+
logger.warning("Button %s has no object to link to", self.label)
|
|
251
|
+
return None
|
|
250
252
|
return reverse(self.link_name, kwargs={"pk": obj.pk})
|
|
251
253
|
elif self.link_name:
|
|
252
254
|
return reverse(self.link_name)
|
|
@@ -310,6 +312,7 @@ class FormButton(Button):
|
|
|
310
312
|
self,
|
|
311
313
|
form_id: str,
|
|
312
314
|
link_name: str,
|
|
315
|
+
render_on_tab_id="__all__",
|
|
313
316
|
template_path="components/button/formbutton.html",
|
|
314
317
|
**kwargs,
|
|
315
318
|
):
|
|
@@ -331,7 +334,7 @@ class FormButton(Button):
|
|
|
331
334
|
if not self.form_id:
|
|
332
335
|
raise ValueError("FormButton requires 'form_id' to be set in ObjectsTablePanel.")
|
|
333
336
|
|
|
334
|
-
super().__init__(link_name=link_name, template_path=template_path, **kwargs)
|
|
337
|
+
super().__init__(link_name=link_name, render_on_tab_id=render_on_tab_id, template_path=template_path, **kwargs)
|
|
335
338
|
|
|
336
339
|
def get_extra_context(self, context: Context):
|
|
337
340
|
return {
|
|
@@ -768,6 +771,7 @@ class ObjectsTablePanel(Panel):
|
|
|
768
771
|
select_related_fields=None,
|
|
769
772
|
prefetch_related_fields=None,
|
|
770
773
|
order_by_fields=None,
|
|
774
|
+
# TODO: Is `table_title` redundant with the base Panel's `label`?
|
|
771
775
|
table_title=None,
|
|
772
776
|
max_display_count=None,
|
|
773
777
|
paginate=True,
|
|
@@ -778,6 +782,8 @@ class ObjectsTablePanel(Panel):
|
|
|
778
782
|
add_permissions=None,
|
|
779
783
|
hide_hierarchy_ui=False,
|
|
780
784
|
related_field_name=None,
|
|
785
|
+
related_list_url_name=None,
|
|
786
|
+
enable_related_link=True,
|
|
781
787
|
enable_bulk_actions=False,
|
|
782
788
|
tab_id=None,
|
|
783
789
|
body_wrapper_template_path="components/panel/body_wrapper_table.html",
|
|
@@ -829,6 +835,11 @@ class ObjectsTablePanel(Panel):
|
|
|
829
835
|
hide_hierarchy_ui (bool, optional): Don't display hierarchy-based indentation of tree models in this table
|
|
830
836
|
related_field_name (str, optional): The name of the filter/form field for the related model that links back
|
|
831
837
|
to the base model. Defaults to the same as `table_filter` if unset. Used to populate URLs.
|
|
838
|
+
related_list_url_name (str, optional): The URL used to generate the list button URL for the related model.
|
|
839
|
+
If not provided, the default table's model `list` route is used.
|
|
840
|
+
This can be useful when the related model is a many-to-many relationship with a custom through table.
|
|
841
|
+
enable_related_link (bool, optional): If True, the badge on the related model will be a link to the related model list view.
|
|
842
|
+
When False, the badge will still show the count of the related model, but will not be a link.
|
|
832
843
|
enable_bulk_actions (bool, optional): Show the pk toggle columns on the table if the user has the
|
|
833
844
|
appropriate permissions.
|
|
834
845
|
tab_id (str, optional): The ID of the tab this panel belongs to. Used to append to a `return_url` when
|
|
@@ -879,6 +890,8 @@ class ObjectsTablePanel(Panel):
|
|
|
879
890
|
self.add_permissions = add_permissions or []
|
|
880
891
|
self.hide_hierarchy_ui = hide_hierarchy_ui
|
|
881
892
|
self.related_field_name = related_field_name
|
|
893
|
+
self.related_list_url_name = related_list_url_name
|
|
894
|
+
self.enable_related_link = enable_related_link
|
|
882
895
|
self.enable_bulk_actions = enable_bulk_actions
|
|
883
896
|
self.tab_id = tab_id
|
|
884
897
|
self.footer_buttons = footer_buttons
|
|
@@ -905,7 +918,12 @@ class ObjectsTablePanel(Panel):
|
|
|
905
918
|
related_field_name = self.related_field_name or self.table_filter or obj._meta.model_name
|
|
906
919
|
return_url = context.get("return_url", obj.get_absolute_url())
|
|
907
920
|
if self.tab_id:
|
|
908
|
-
|
|
921
|
+
try:
|
|
922
|
+
# Check to see if the this is a NautobotUIViewset action
|
|
923
|
+
view = get_view_for_model(obj._meta.model)
|
|
924
|
+
return_url += getattr(view, self.tab_id).url_path + "/"
|
|
925
|
+
except AttributeError:
|
|
926
|
+
return_url += f"?tab={self.tab_id}"
|
|
909
927
|
|
|
910
928
|
if self.add_button_route is not None:
|
|
911
929
|
add_permissions = self.add_permissions
|
|
@@ -1034,29 +1052,35 @@ class ObjectsTablePanel(Panel):
|
|
|
1034
1052
|
body_content_table_model = body_content_table.Meta.model
|
|
1035
1053
|
related_field_name = self.related_field_name or self.table_filter or obj._meta.model_name
|
|
1036
1054
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1055
|
+
body_content_table_list_url = None
|
|
1056
|
+
body_content_table_add_url = self._get_table_add_url(context)
|
|
1057
|
+
table_title = self.table_title or body_content_table_model._meta.verbose_name_plural
|
|
1040
1058
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1059
|
+
if self.enable_related_link:
|
|
1060
|
+
list_url = self.related_list_url_name or getattr(self.table_class, "list_url", None)
|
|
1061
|
+
if not list_url:
|
|
1062
|
+
list_url = get_route_for_model(body_content_table_model, "list")
|
|
1045
1063
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1064
|
+
try:
|
|
1065
|
+
list_route = reverse(list_url)
|
|
1066
|
+
except NoReverseMatch:
|
|
1067
|
+
logger.warning(
|
|
1068
|
+
f"Unable to determine a valid list URL for ObjectsTablePanel `{table_title}`"
|
|
1069
|
+
f" related to `{body_content_table_model.__name__}` with `{list_url}`."
|
|
1070
|
+
" If the related object is using a through table, consider setting the `related_list_url_name`"
|
|
1071
|
+
" parameter or disabling the related link via 'enable_related_link=False'."
|
|
1072
|
+
)
|
|
1073
|
+
list_route = None
|
|
1050
1074
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1075
|
+
if list_route:
|
|
1076
|
+
body_content_table_list_url = f"{list_route}?{related_field_name}={obj.pk}"
|
|
1053
1077
|
|
|
1054
1078
|
return {
|
|
1055
1079
|
"body_content_table": body_content_table,
|
|
1056
1080
|
"body_content_table_add_url": body_content_table_add_url,
|
|
1057
1081
|
"body_content_table_list_url": body_content_table_list_url,
|
|
1058
1082
|
"body_content_table_verbose_name": body_content_table_model._meta.verbose_name,
|
|
1059
|
-
"body_content_table_verbose_name_plural":
|
|
1083
|
+
"body_content_table_verbose_name_plural": table_title,
|
|
1060
1084
|
"footer_buttons": self.footer_buttons,
|
|
1061
1085
|
"form_id": self.form_id,
|
|
1062
1086
|
"more_queryset_count": more_queryset_count,
|
|
@@ -1316,24 +1340,6 @@ class EChartsPanel(Panel, EChartsBase):
|
|
|
1316
1340
|
super().__init__(body_wrapper_template_path=body_wrapper_template_path, body_id=self.body_id, **kwargs)
|
|
1317
1341
|
EChartsBase.__init__(self, **chart_kwargs)
|
|
1318
1342
|
|
|
1319
|
-
def get_data(self, context: Context) -> dict[str, Any] | None:
|
|
1320
|
-
"""Get the data for chart.
|
|
1321
|
-
|
|
1322
|
-
Args:
|
|
1323
|
-
context (Context): The template or request context.
|
|
1324
|
-
|
|
1325
|
-
Returns:
|
|
1326
|
-
dict[str, Any] | None:
|
|
1327
|
-
- A dictionary in internal chart format, e.g.:
|
|
1328
|
-
{"x": [...], "series": [{"name": str, "data": [...]}]}
|
|
1329
|
-
- A nested dictionary of series, e.g.:
|
|
1330
|
-
{"Series1": {"x1": val1, "x2": val2}, ...}
|
|
1331
|
-
- `None` if no data is set.
|
|
1332
|
-
"""
|
|
1333
|
-
if callable(self.data):
|
|
1334
|
-
return self.data(context) # pylint: disable=not-callable
|
|
1335
|
-
return self.data
|
|
1336
|
-
|
|
1337
1343
|
def should_render(self, context: Context):
|
|
1338
1344
|
"""Determine if the panel should be rendered."""
|
|
1339
1345
|
if not super().should_render(context):
|
|
@@ -1349,8 +1355,7 @@ class EChartsPanel(Panel, EChartsBase):
|
|
|
1349
1355
|
|
|
1350
1356
|
def get_extra_context(self, context: Context):
|
|
1351
1357
|
"""Add chart-specific context variables."""
|
|
1352
|
-
|
|
1353
|
-
chart_config = self.get_config()
|
|
1358
|
+
chart_config = self.get_config(context=context)
|
|
1354
1359
|
return {
|
|
1355
1360
|
**super().get_extra_context(context),
|
|
1356
1361
|
"chart": self,
|
|
@@ -1765,7 +1770,7 @@ class StatsPanel(Panel):
|
|
|
1765
1770
|
value = [related_object_list_url, related_object_count, related_object_title]
|
|
1766
1771
|
stats[related_object_model_class] = value
|
|
1767
1772
|
related_object_model_filterset = get_filterset_for_model(related_object_model_class)
|
|
1768
|
-
if self.filter_name not in related_object_model_filterset.
|
|
1773
|
+
if self.filter_name not in related_object_model_filterset.base_filters:
|
|
1769
1774
|
raise FieldDoesNotExist(
|
|
1770
1775
|
f"{self.filter_name} is not a valid filter field for {related_object_model_class_meta.verbose_name}"
|
|
1771
1776
|
)
|
|
@@ -2144,8 +2149,8 @@ class _ObjectDetailContactsTab(Tab):
|
|
|
2144
2149
|
max_display_count=100, # since there isn't a separate list view for ContactAssociations!
|
|
2145
2150
|
# TODO: we should provide a standard reusable component template for bulk-actions in the footer
|
|
2146
2151
|
footer_content_template_path="components/panel/footer_contacts_table.html",
|
|
2147
|
-
|
|
2148
|
-
|
|
2152
|
+
enable_related_link=False,
|
|
2153
|
+
table_title="Contacts/Teams",
|
|
2149
2154
|
),
|
|
2150
2155
|
)
|
|
2151
2156
|
super().__init__(tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
|
|
@@ -2185,9 +2190,8 @@ class _ObjectDetailDataComplianceTab(DistinctViewTab):
|
|
|
2185
2190
|
table_class=DataComplianceTable,
|
|
2186
2191
|
table_attribute="associated_data_compliance",
|
|
2187
2192
|
related_field_name="object_id",
|
|
2188
|
-
|
|
2193
|
+
table_title="Data Compliance",
|
|
2189
2194
|
add_button_route=None,
|
|
2190
|
-
header_extra_content_template_path=None,
|
|
2191
2195
|
include_paginator=True,
|
|
2192
2196
|
),
|
|
2193
2197
|
)
|
|
@@ -2199,7 +2203,12 @@ class _ObjectDetailDataComplianceTab(DistinctViewTab):
|
|
|
2199
2203
|
def should_render(self, context: Context):
|
|
2200
2204
|
if not super().should_render(context):
|
|
2201
2205
|
return False
|
|
2202
|
-
|
|
2206
|
+
obj = get_obj_from_context(context)
|
|
2207
|
+
if getattr(obj, "is_data_compliance_model", False):
|
|
2208
|
+
if obj.get_data_compliance_url() is not None:
|
|
2209
|
+
return True
|
|
2210
|
+
logger.warning("Missing data-compliance URL for %r", obj)
|
|
2211
|
+
return False
|
|
2203
2212
|
|
|
2204
2213
|
|
|
2205
2214
|
class DynamicGroupsTextPanel(BaseTextPanel):
|
|
@@ -2307,8 +2316,7 @@ class _ObjectDetailMetadataTab(Tab):
|
|
|
2307
2316
|
exclude_columns=["assigned_object"],
|
|
2308
2317
|
add_button_route=None,
|
|
2309
2318
|
related_field_name="assigned_object_id",
|
|
2310
|
-
|
|
2311
|
-
label="Object Metadata",
|
|
2319
|
+
table_title="Object Metadata",
|
|
2312
2320
|
),
|
|
2313
2321
|
)
|
|
2314
2322
|
super().__init__(
|
nautobot/core/ui/titles.py
CHANGED
|
@@ -6,17 +6,14 @@ from django.utils.html import strip_tags
|
|
|
6
6
|
DEFAULT_TITLES: dict[str, str] = {
|
|
7
7
|
"*": "{{ verbose_name_plural|bettertitle }}",
|
|
8
8
|
"list": "{{ verbose_name_plural|bettertitle }}",
|
|
9
|
-
"detail": "{{ object.
|
|
10
|
-
"retrieve": "{{ object.
|
|
9
|
+
"detail": "{{ object.page_title|default:object }}",
|
|
10
|
+
"retrieve": "{{ object.page_title|default:object }}",
|
|
11
11
|
"destroy": "Delete {{ verbose_name }}?",
|
|
12
12
|
"create": "Add a new {{ verbose_name }}",
|
|
13
|
-
"update": "Editing {{ verbose_name }} {{ object.
|
|
13
|
+
"update": "Editing {{ verbose_name }} {{ object.page_title|default:object }}",
|
|
14
14
|
"bulk_destroy": "Delete {{ total_objs_to_delete }} {{ verbose_name_plural|bettertitle }}?",
|
|
15
15
|
"bulk_rename": "Renaming {{ selected_objects|length }} {{ verbose_name_plural|bettertitle }} on {{ parent_name }}",
|
|
16
16
|
"bulk_update": "Editing {{ objs_count }} {{ verbose_name_plural|bettertitle }}",
|
|
17
|
-
"changelog": "{{ object.display|default:object }} - Change Log",
|
|
18
|
-
"config_context": "{{ object.display|default:object }} - Config Context",
|
|
19
|
-
"notes": "{{ object.display|default:object }} - Notes",
|
|
20
17
|
"approve": "Approve {{ verbose_name|bettertitle }}?",
|
|
21
18
|
"deny": "Deny {{ verbose_name|bettertitle }}?",
|
|
22
19
|
}
|
nautobot/core/urls.py
CHANGED
|
@@ -42,6 +42,7 @@ urlpatterns = [
|
|
|
42
42
|
path("dcim/", include("nautobot.dcim.urls")),
|
|
43
43
|
path("extras/", include("nautobot.extras.urls")),
|
|
44
44
|
path("ipam/", include("nautobot.ipam.urls")),
|
|
45
|
+
path("load-balancers/", include("nautobot.load_balancers.urls")),
|
|
45
46
|
path("tenancy/", include("nautobot.tenancy.urls")),
|
|
46
47
|
# TODO: deprecate this url and use users
|
|
47
48
|
path("user/", include("nautobot.users.urls")),
|
|
@@ -103,15 +104,14 @@ urlpatterns = [
|
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
if settings.DEBUG:
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
urlpatterns += [path("theme-preview/", ThemePreviewView.as_view(), name="theme_preview")]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if "debug_toolbar" in settings.INSTALLED_APPS:
|
|
111
|
+
from debug_toolbar.toolbar import debug_toolbar_urls
|
|
112
|
+
|
|
113
|
+
urlpatterns += debug_toolbar_urls()
|
|
108
114
|
|
|
109
|
-
urlpatterns += [
|
|
110
|
-
path("__debug__/", include(debug_toolbar.urls)),
|
|
111
|
-
path("theme-preview/", ThemePreviewView.as_view(), name="theme_preview"),
|
|
112
|
-
]
|
|
113
|
-
except ImportError:
|
|
114
|
-
pass
|
|
115
115
|
|
|
116
116
|
if settings.METRICS_ENABLED:
|
|
117
117
|
if settings.METRICS_AUTHENTICATED:
|
nautobot/core/utils/filtering.py
CHANGED
|
@@ -107,6 +107,7 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
|
|
|
107
107
|
BOOLEAN_CHOICES,
|
|
108
108
|
DynamicModelMultipleChoiceField,
|
|
109
109
|
MultipleContentTypeField,
|
|
110
|
+
MultiValueCharInput,
|
|
110
111
|
StaticSelect2,
|
|
111
112
|
StaticSelect2Multiple,
|
|
112
113
|
)
|
|
@@ -127,7 +128,16 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
|
|
|
127
128
|
elif isinstance(field, (MultiValueDecimalFilter, MultiValueFloatFilter)):
|
|
128
129
|
form_field = forms.DecimalField()
|
|
129
130
|
elif isinstance(field, NumberFilter):
|
|
130
|
-
|
|
131
|
+
# If "choices" are passed, then when 'exact' is used in an Advanced
|
|
132
|
+
# Filter, render a dropdown of choices instead of a free integer input
|
|
133
|
+
if field.lookup_expr == "exact" and getattr(field, "choices", None):
|
|
134
|
+
# Use a multi-value widget that allows both preset choices and free-form entries
|
|
135
|
+
form_field = forms.MultipleChoiceField(
|
|
136
|
+
choices=field.choices,
|
|
137
|
+
widget=MultiValueCharInput,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
form_field = forms.IntegerField()
|
|
131
141
|
elif isinstance(field, ModelMultipleChoiceFilter):
|
|
132
142
|
if getattr(field, "prefers_id", False):
|
|
133
143
|
to_field_name = "id"
|
nautobot/core/utils/lookup.py
CHANGED
|
@@ -61,6 +61,14 @@ def resolve_attr(obj, dotted_field):
|
|
|
61
61
|
return str(val) if val else None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def get_breadcrumbs_for_model(model, view_type: str = "List"):
|
|
65
|
+
"""Get a UI Component Framework 'Breadcrumbs' instance for the given model's related UIViewSet or generic view."""
|
|
66
|
+
view = get_view_for_model(model)
|
|
67
|
+
if hasattr(view, "get_breadcrumbs"):
|
|
68
|
+
return view.get_breadcrumbs(model, view_type=view_type)
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
64
72
|
def get_changes_for_model(model):
|
|
65
73
|
"""
|
|
66
74
|
Return a queryset of ObjectChanges for a model or instance. The queryset will be filtered
|
|
@@ -78,6 +86,30 @@ def get_changes_for_model(model):
|
|
|
78
86
|
raise TypeError(f"{model!r} is not a Django Model class or instance")
|
|
79
87
|
|
|
80
88
|
|
|
89
|
+
def get_detail_view_components_context_for_model(model) -> dict:
|
|
90
|
+
"""Helper method for DistinctViewTabs etc. to retrieve the UI Component Framework context for the base detail view.
|
|
91
|
+
|
|
92
|
+
Functionally equivalent to calling `get_breadcrumbs_for_model()`, `get_object_detail_content_for_model()`, and
|
|
93
|
+
`get_view_titles_for_model()`, but marginally more efficient.
|
|
94
|
+
"""
|
|
95
|
+
context = {
|
|
96
|
+
"breadcrumbs": None,
|
|
97
|
+
"object_detail_content": None,
|
|
98
|
+
"view_titles": None,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
view = get_view_for_model(model, view_type="")
|
|
102
|
+
if view is not None:
|
|
103
|
+
if hasattr(view, "get_breadcrumbs"):
|
|
104
|
+
context["breadcrumbs"] = view.get_breadcrumbs(model, view_type="")
|
|
105
|
+
if hasattr(view, "get_view_titles"):
|
|
106
|
+
context["view_titles"] = view.get_view_titles(model, view_type="")
|
|
107
|
+
if hasattr(view, "object_detail_content"):
|
|
108
|
+
context["object_detail_content"] = view.object_detail_content
|
|
109
|
+
|
|
110
|
+
return context
|
|
111
|
+
|
|
112
|
+
|
|
81
113
|
def get_model_from_name(model_name):
|
|
82
114
|
"""Given a full model name in dotted format (example: `dcim.model`), a model class is returned if valid.
|
|
83
115
|
|
|
@@ -222,6 +254,12 @@ def get_form_for_model(model, form_prefix=""):
|
|
|
222
254
|
return get_related_class_for_model(model, module_name="forms", object_suffix=object_suffix)
|
|
223
255
|
|
|
224
256
|
|
|
257
|
+
def get_object_detail_content_for_model(model):
|
|
258
|
+
"""Get the UI Component Framework 'object_detail_content' for the given model's related UIViewSet or ObjectView."""
|
|
259
|
+
view = get_view_for_model(model)
|
|
260
|
+
return getattr(view, "object_detail_content", None)
|
|
261
|
+
|
|
262
|
+
|
|
225
263
|
def get_related_field_for_models(from_model, to_model):
|
|
226
264
|
"""
|
|
227
265
|
Find the field on `from_model` that is a relation to `to_model`.
|
|
@@ -289,6 +327,14 @@ def get_view_for_model(model, view_type=""):
|
|
|
289
327
|
return result
|
|
290
328
|
|
|
291
329
|
|
|
330
|
+
def get_view_titles_for_model(model, view_type: str = "List"):
|
|
331
|
+
"""Get a UI Component Framework 'Titles' instance for the given model's related UIViewSet or generic view."""
|
|
332
|
+
view = get_view_for_model(model)
|
|
333
|
+
if hasattr(view, "get_view_titles"):
|
|
334
|
+
return view.get_view_titles(model, view_type=view_type)
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
|
|
292
338
|
def get_model_for_view_name(view_name):
|
|
293
339
|
"""
|
|
294
340
|
Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -263,52 +263,57 @@ class UIComponentsMixin:
|
|
|
263
263
|
breadcrumbs: ClassVar[Optional[Breadcrumbs]] = None
|
|
264
264
|
view_titles: ClassVar[Optional[Titles]] = None
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
@classmethod
|
|
267
|
+
def get_view_titles(cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List") -> Titles:
|
|
267
268
|
"""
|
|
268
269
|
Resolve and return the `Titles` component instance.
|
|
269
270
|
|
|
270
271
|
Resolution order:
|
|
271
|
-
1) If
|
|
272
|
-
2) Else, if `model` is provided, copy the `view_titles` from the view
|
|
273
|
-
|
|
272
|
+
1) If `.view_titles` is set on the current view, use it.
|
|
273
|
+
2) Else, if `model` is provided, copy the `view_titles` from the view class associated with that model
|
|
274
|
+
via `lookup.get_view_for_model(model, action)`.
|
|
274
275
|
3) Else, instantiate and return the default `Titles()`.
|
|
275
276
|
|
|
276
277
|
Args:
|
|
277
278
|
model: A Django model **class**, **instance**, dotted name string, or `None`.
|
|
278
279
|
Passed to `lookup.get_view_for_model()` to find the related view class.
|
|
279
280
|
If `None`, only local/default resolution is used.
|
|
280
|
-
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
281
|
+
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
282
|
+
(e.g., `"List"` or empty to construct `"DeviceView"` string).
|
|
281
283
|
|
|
282
284
|
Returns:
|
|
283
285
|
Titles: A concrete `Titles` component instance ready to use.
|
|
284
286
|
"""
|
|
285
|
-
return
|
|
287
|
+
return cls._resolve_component("view_titles", Titles, model, view_type)
|
|
286
288
|
|
|
289
|
+
@classmethod
|
|
287
290
|
def get_breadcrumbs(
|
|
288
|
-
|
|
291
|
+
cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List"
|
|
289
292
|
) -> Breadcrumbs:
|
|
290
293
|
"""
|
|
291
294
|
Resolve and return the `Breadcrumbs` component instance.
|
|
292
295
|
|
|
293
296
|
Resolution order mirrors `get_view_titles()`:
|
|
294
|
-
1) Use
|
|
295
|
-
2) Else, if `model` is provided, copy the `breadcrumbs` from the view
|
|
296
|
-
|
|
297
|
+
1) Use `.breadcrumbs` if set locally.
|
|
298
|
+
2) Else, if `model` is provided, copy the `breadcrumbs` from the view class associated with that model
|
|
299
|
+
via `lookup.get_view_for_model(model, action)`.
|
|
297
300
|
3) Else return a new default `Breadcrumbs()`.
|
|
298
301
|
|
|
299
302
|
Args:
|
|
300
303
|
model: A Django model **class**, **instance**, dotted name string, or `None`.
|
|
301
304
|
Passed to `lookup.get_view_for_model()` to find the related view class.
|
|
302
305
|
If `None`, only local/default resolution is used.
|
|
303
|
-
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
306
|
+
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
307
|
+
(e.g., `"List"` or empty to construct `"DeviceView"` string).
|
|
304
308
|
|
|
305
309
|
Returns:
|
|
306
310
|
Breadcrumbs: A concrete `Breadcrumbs` component instance.
|
|
307
311
|
"""
|
|
308
|
-
return
|
|
312
|
+
return cls._resolve_component("breadcrumbs", Breadcrumbs, model, view_type)
|
|
309
313
|
|
|
314
|
+
@classmethod
|
|
310
315
|
def _resolve_component(
|
|
311
|
-
|
|
316
|
+
cls,
|
|
312
317
|
attr_name: str,
|
|
313
318
|
default_cls: Type[Union[Breadcrumbs, Titles]],
|
|
314
319
|
model: Union[None, str, Type[Model], Model] = None,
|
|
@@ -329,14 +334,14 @@ class UIComponentsMixin:
|
|
|
329
334
|
Returns:
|
|
330
335
|
Breadcrumbs/Title instance.
|
|
331
336
|
"""
|
|
332
|
-
local = getattr(
|
|
337
|
+
local = getattr(cls, attr_name, None)
|
|
333
338
|
if local is not None:
|
|
334
|
-
return
|
|
339
|
+
return cls._instantiate_if_needed(local, default_cls)
|
|
335
340
|
|
|
336
341
|
if model is not None:
|
|
337
342
|
view_class = lookup.get_view_for_model(model, view_type)
|
|
338
343
|
view_component = getattr(view_class, attr_name, None)
|
|
339
|
-
return
|
|
344
|
+
return cls._instantiate_if_needed(view_component, default_cls)
|
|
340
345
|
|
|
341
346
|
return default_cls()
|
|
342
347
|
|
|
@@ -732,7 +737,11 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
|
|
|
732
737
|
except TemplateDoesNotExist:
|
|
733
738
|
# Try a different detail view template format
|
|
734
739
|
template_name = f"{app_label}/{model_opts.model_name}.html"
|
|
735
|
-
|
|
740
|
+
try:
|
|
741
|
+
select_template([template_name])
|
|
742
|
+
except TemplateDoesNotExist:
|
|
743
|
+
# Catch-all fallback to just object_retrieve.html
|
|
744
|
+
template_name = "generic/object_retrieve.html"
|
|
736
745
|
return template_name
|
|
737
746
|
|
|
738
747
|
def get_form(self, *args, **kwargs):
|
|
@@ -1500,7 +1509,9 @@ class ObjectChangeLogViewMixin(NautobotViewSetMixin):
|
|
|
1500
1509
|
|
|
1501
1510
|
base_template: Optional[str] = None
|
|
1502
1511
|
|
|
1503
|
-
@drf_action(
|
|
1512
|
+
@drf_action(
|
|
1513
|
+
detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_objectchange"]
|
|
1514
|
+
)
|
|
1504
1515
|
def changelog(self, request, *args, **kwargs):
|
|
1505
1516
|
model = self.get_queryset().model
|
|
1506
1517
|
data = {
|
|
@@ -1521,7 +1532,7 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
|
|
|
1521
1532
|
|
|
1522
1533
|
base_template: Optional[str] = None
|
|
1523
1534
|
|
|
1524
|
-
@drf_action(detail=True)
|
|
1535
|
+
@drf_action(detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_note"])
|
|
1525
1536
|
def notes(self, request, *args, **kwargs):
|
|
1526
1537
|
model = self.get_queryset().model
|
|
1527
1538
|
data = {
|
|
@@ -1536,6 +1547,6 @@ class ObjectDataComplianceViewMixin(NautobotViewSetMixin):
|
|
|
1536
1547
|
UI Mixin for a DataCompliance to show up for a given object.
|
|
1537
1548
|
"""
|
|
1538
1549
|
|
|
1539
|
-
@drf_action(detail=True)
|
|
1550
|
+
@drf_action(detail=True, url_path="data-compliance")
|
|
1540
1551
|
def data_compliance(self, request, *args, **kwargs):
|
|
1541
1552
|
return Response({})
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -15,7 +15,7 @@ from nautobot.core.forms import (
|
|
|
15
15
|
TableConfigForm,
|
|
16
16
|
)
|
|
17
17
|
from nautobot.core.forms.forms import DynamicFilterFormSet
|
|
18
|
-
from nautobot.core.templatetags.helpers import
|
|
18
|
+
from nautobot.core.templatetags.helpers import validated_viewname
|
|
19
19
|
from nautobot.core.utils.config import get_settings_or_config
|
|
20
20
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
21
21
|
from nautobot.core.utils.requests import (
|
|
@@ -227,7 +227,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
227
227
|
if view.filterset is not None:
|
|
228
228
|
filterset_filters = view.filterset.filters
|
|
229
229
|
else:
|
|
230
|
-
filterset_filters = view.filterset_class.
|
|
230
|
+
filterset_filters = view.filterset_class.base_filters
|
|
231
231
|
display_filter_params = [
|
|
232
232
|
check_filter_for_display(filterset_filters, field_name, values)
|
|
233
233
|
for field_name, values in view.filter_params.items()
|
|
@@ -324,7 +324,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
324
324
|
"action_buttons": valid_actions,
|
|
325
325
|
"list_url": list_url,
|
|
326
326
|
"saved_views": saved_views,
|
|
327
|
-
"title": bettertitle(model._meta.verbose_name_plural),
|
|
328
327
|
}
|
|
329
328
|
)
|
|
330
329
|
elif view.action in ["create", "update"]:
|
|
@@ -13,7 +13,8 @@ def update_data_validation_engine_job_module_name(apps, schema_editor):
|
|
|
13
13
|
"""
|
|
14
14
|
Job = apps.get_model("extras", "Job")
|
|
15
15
|
dve_jobs = Job.objects.filter(module_name="nautobot_data_validation_engine.jobs")
|
|
16
|
-
|
|
16
|
+
# Now that the DVE jobs are system jobs, they should be enabled by default
|
|
17
|
+
dve_jobs.update(module_name="nautobot.core.jobs", enabled=True)
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def update_data_validation_engine_git_repo_contents(apps, schema_editor):
|
|
@@ -170,7 +171,7 @@ def revert_data_validation_engine_job_module_name(apps, schema_editor):
|
|
|
170
171
|
Revert the `module_name` for the Jobs to match the old location of the data validation engine.
|
|
171
172
|
"""
|
|
172
173
|
Job = apps.get_model("extras", "Job")
|
|
173
|
-
dve_jobs = Job.objects.filter(module_name="nautobot.
|
|
174
|
+
dve_jobs = Job.objects.filter(module_name="nautobot.core.jobs")
|
|
174
175
|
dve_jobs.update(module_name="nautobot_data_validation_engine.jobs")
|
|
175
176
|
|
|
176
177
|
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -30,6 +30,7 @@ from nautobot.dcim.choices import (
|
|
|
30
30
|
ControllerCapabilitiesChoices,
|
|
31
31
|
DeviceFaceChoices,
|
|
32
32
|
DeviceRedundancyGroupFailoverStrategyChoices,
|
|
33
|
+
InterfaceDuplexChoices,
|
|
33
34
|
InterfaceModeChoices,
|
|
34
35
|
InterfaceRedundancyGroupProtocolChoices,
|
|
35
36
|
InterfaceTypeChoices,
|
|
@@ -693,6 +694,8 @@ class InterfaceSerializer(
|
|
|
693
694
|
mode = ChoiceField(choices=InterfaceModeChoices, allow_blank=True, required=False)
|
|
694
695
|
mac_address = serializers.CharField(allow_blank=True, allow_null=True, required=False)
|
|
695
696
|
ip_address_count = serializers.IntegerField(read_only=True, source="_ip_address_count")
|
|
697
|
+
speed = serializers.IntegerField(required=False, allow_null=True)
|
|
698
|
+
duplex = ChoiceField(choices=InterfaceDuplexChoices, allow_blank=True, required=False)
|
|
696
699
|
|
|
697
700
|
class Meta:
|
|
698
701
|
model = Interface
|
nautobot/dcim/choices.py
CHANGED
|
@@ -1154,6 +1154,55 @@ class InterfaceModeChoices(ChoiceSet):
|
|
|
1154
1154
|
)
|
|
1155
1155
|
|
|
1156
1156
|
|
|
1157
|
+
class InterfaceDuplexChoices(ChoiceSet):
|
|
1158
|
+
DUPLEX_AUTO = "auto"
|
|
1159
|
+
DUPLEX_FULL = "full"
|
|
1160
|
+
DUPLEX_HALF = "half"
|
|
1161
|
+
|
|
1162
|
+
CHOICES = (
|
|
1163
|
+
(DUPLEX_AUTO, "Auto"),
|
|
1164
|
+
(DUPLEX_FULL, "Full"),
|
|
1165
|
+
(DUPLEX_HALF, "Half"),
|
|
1166
|
+
)
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
class InterfaceSpeedChoices(ChoiceSet):
|
|
1170
|
+
# Stored in Kbps (for compatibility with circuits and humanize_speed filter)
|
|
1171
|
+
SPEED_1M = 1_000
|
|
1172
|
+
SPEED_10M = 10_000
|
|
1173
|
+
SPEED_100M = 100_000
|
|
1174
|
+
SPEED_1G = 1_000_000
|
|
1175
|
+
SPEED_2_5G = 2_500_000
|
|
1176
|
+
SPEED_5G = 5_000_000
|
|
1177
|
+
SPEED_10G = 10_000_000
|
|
1178
|
+
SPEED_25G = 25_000_000
|
|
1179
|
+
SPEED_40G = 40_000_000
|
|
1180
|
+
SPEED_50G = 50_000_000
|
|
1181
|
+
SPEED_100G = 100_000_000
|
|
1182
|
+
SPEED_200G = 200_000_000
|
|
1183
|
+
SPEED_400G = 400_000_000
|
|
1184
|
+
SPEED_800G = 800_000_000
|
|
1185
|
+
SPEED_1_6T = 1_600_000_000
|
|
1186
|
+
|
|
1187
|
+
CHOICES = (
|
|
1188
|
+
(SPEED_1M, "1 Mbps"),
|
|
1189
|
+
(SPEED_10M, "10 Mbps"),
|
|
1190
|
+
(SPEED_100M, "100 Mbps"),
|
|
1191
|
+
(SPEED_1G, "1 Gbps"),
|
|
1192
|
+
(SPEED_2_5G, "2.5 Gbps"),
|
|
1193
|
+
(SPEED_5G, "5 Gbps"),
|
|
1194
|
+
(SPEED_10G, "10 Gbps"),
|
|
1195
|
+
(SPEED_25G, "25 Gbps"),
|
|
1196
|
+
(SPEED_40G, "40 Gbps"),
|
|
1197
|
+
(SPEED_50G, "50 Gbps"),
|
|
1198
|
+
(SPEED_100G, "100 Gbps"),
|
|
1199
|
+
(SPEED_200G, "200 Gbps"),
|
|
1200
|
+
(SPEED_400G, "400 Gbps"),
|
|
1201
|
+
(SPEED_800G, "800 Gbps"),
|
|
1202
|
+
(SPEED_1_6T, "1.6 Tbps"),
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
|
|
1157
1206
|
class InterfaceStatusChoices(ChoiceSet):
|
|
1158
1207
|
STATUS_PLANNED = "planned"
|
|
1159
1208
|
STATUS_ACTIVE = "active"
|
nautobot/dcim/constants.py
CHANGED
|
@@ -37,6 +37,13 @@ VIRTUAL_IFACE_TYPES = interface_type_by_category["Virtual interfaces"]
|
|
|
37
37
|
|
|
38
38
|
NONCONNECTABLE_IFACE_TYPES = VIRTUAL_IFACE_TYPES + WIRELESS_IFACE_TYPES
|
|
39
39
|
|
|
40
|
+
COPPER_TWISTED_PAIR_IFACE_TYPES = [
|
|
41
|
+
InterfaceTypeChoices.TYPE_100ME_FIXED,
|
|
42
|
+
InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
43
|
+
InterfaceTypeChoices.TYPE_2GE_FIXED,
|
|
44
|
+
InterfaceTypeChoices.TYPE_5GE_FIXED,
|
|
45
|
+
InterfaceTypeChoices.TYPE_10GE_FIXED,
|
|
46
|
+
]
|
|
40
47
|
|
|
41
48
|
#
|
|
42
49
|
# PowerFeeds
|
nautobot/dcim/factory.py
CHANGED
|
@@ -186,7 +186,7 @@ class DeviceFactory(PrimaryModelFactory):
|
|
|
186
186
|
)
|
|
187
187
|
device_redundancy_group_priority = factory.Maybe(
|
|
188
188
|
"has_device_redundancy_group",
|
|
189
|
-
factory.Faker("pyint", min_value=1, max_value=
|
|
189
|
+
factory.Faker("pyint", min_value=1, max_value=65535),
|
|
190
190
|
)
|
|
191
191
|
|
|
192
192
|
controller_managed_device_group = random_instance(ControllerManagedDeviceGroup)
|