nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/choices.py +4 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +13 -1
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/cloud/filters.py +1 -1
- nautobot/cloud/navigation.py +3 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +6 -1
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +12 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
- nautobot/core/cli/migrate_deprecated_templates.py +227 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +62 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +35 -19
- nautobot/core/settings.yaml +17 -33
- nautobot/core/signals.py +12 -1
- nautobot/core/tables.py +13 -6
- nautobot/core/templates/40x.html +1 -1
- nautobot/core/templates/500.html +2 -2
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/buttons/export.html +1 -1
- nautobot/core/templates/components/button/dropdown.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +6 -7
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +22 -9
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +6 -5
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_bulk_remove.html +2 -2
- nautobot/core/templates/generic/object_bulk_update.html +5 -4
- nautobot/core/templates/generic/object_create.html +5 -4
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/generic/object_import.html +2 -1
- nautobot/core/templates/generic/object_list.html +12 -4
- nautobot/core/templates/generic/object_notes.html +5 -3
- nautobot/core/templates/generic/object_retrieve.html +4 -5
- nautobot/core/templates/graphene/graphiql.html +7 -8
- nautobot/core/templates/home.html +1 -1
- nautobot/core/templates/import_success.html +2 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/created_updated.html +7 -3
- nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +11 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +3 -9
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -11
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +17 -3
- nautobot/core/templates/system_jobs/import_objects.html +1 -2
- nautobot/core/templates/utilities/confirmation_form.html +2 -2
- nautobot/core/templates/utilities/obj_table.html +10 -2
- nautobot/core/templates/utilities/render_field.html +7 -7
- nautobot/core/templates/utilities/render_jinja2.html +2 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
- nautobot/core/templates/utilities/theme_preview.html +19 -3
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +76 -18
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +41 -17
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -13
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +8 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +27 -28
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_forms.py +55 -13
- nautobot/core/tests/test_jobs.py +144 -3
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +76 -1
- nautobot/core/tests/test_patch_social_django.py +42 -0
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +62 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +123 -4
- nautobot/core/tests/test_utils.py +72 -5
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +70 -29
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +143 -27
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +143 -55
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +20 -9
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +28 -18
- nautobot/core/utils/lookup.py +49 -8
- nautobot/core/utils/module_loading.py +21 -0
- nautobot/core/utils/patch_social_django.py +128 -0
- nautobot/core/views/__init__.py +38 -1
- nautobot/core/views/generic.py +3 -3
- nautobot/core/views/mixins.py +45 -22
- nautobot/core/views/renderers.py +4 -3
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/apps.py +1 -5
- nautobot/data_validation/custom_validators.py +4 -4
- nautobot/data_validation/filters.py +1 -1
- nautobot/data_validation/forms.py +40 -0
- nautobot/data_validation/migrations/0001_initial.py +0 -7
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
- nautobot/data_validation/models.py +16 -7
- nautobot/data_validation/navigation.py +8 -1
- nautobot/data_validation/tables.py +12 -5
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +2 -2
- nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
- nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
- nautobot/data_validation/tests/test_filters.py +8 -6
- nautobot/data_validation/tests/test_models.py +15 -0
- nautobot/data_validation/tests/test_views.py +190 -32
- nautobot/data_validation/urls.py +2 -5
- nautobot/data_validation/views.py +73 -40
- nautobot/dcim/api/serializers.py +3 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +65 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
- nautobot/dcim/forms.py +90 -4
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +98 -64
- nautobot/dcim/models/devices.py +30 -20
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +18 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +15 -15
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_trace.html +0 -4
- nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
- nautobot/dcim/templates/dcim/consoleport.html +7 -6
- nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_component_add.html +2 -2
- nautobot/dcim/templates/dcim/device_create.html +5 -3
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/device_list.html +73 -10
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/frontport.html +10 -9
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +35 -7
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +6 -5
- nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
- nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
- nautobot/dcim/templates/dcim/rearport.html +8 -7
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
- nautobot/dcim/tests/integration/test_controller.py +4 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +194 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +55 -7
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +328 -4
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +132 -29
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +777 -397
- nautobot/extras/api/views.py +60 -45
- nautobot/extras/choices.py +2 -13
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
- nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
- nautobot/extras/{filters/__init__.py → filters.py} +33 -48
- nautobot/extras/forms/forms.py +14 -15
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +11 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
- nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
- nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
- nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +33 -14
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +60 -4
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +23 -2
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/navigation.py +13 -9
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -9
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +64 -68
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/approval_dashboard.html +7 -5
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
- nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
- nautobot/extras/templates/extras/inc/json_format.html +10 -3
- nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
- nautobot/extras/templates/extras/job.html +29 -12
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
- nautobot/extras/templates/extras/job_edit.html +52 -46
- nautobot/extras/templates/extras/job_list.html +29 -25
- nautobot/extras/templates/extras/marketplace.html +5 -9
- nautobot/extras/templates/extras/object_configcontext.html +1 -1
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
- nautobot/extras/templates/extras/plugin_detail.html +29 -24
- nautobot/extras/templates/extras/plugins_list.html +16 -26
- nautobot/extras/templates/extras/role_retrieve.html +64 -0
- nautobot/extras/templates/extras/scheduledjob.html +4 -2
- nautobot/extras/templates/extras/secret_create.html +1 -1
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +9 -10
- nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +90 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +59 -5
- nautobot/extras/tests/test_customfields.py +22 -13
- nautobot/extras/tests/test_customfields_filters.py +479 -0
- nautobot/extras/tests/test_dynamicgroups.py +39 -1
- nautobot/extras/tests/test_filters.py +57 -22
- nautobot/extras/tests/test_forms.py +18 -21
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +1 -0
- nautobot/extras/tests/test_models.py +51 -33
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +52 -112
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +164 -71
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +53 -38
- nautobot/ipam/formfields.py +1 -1
- nautobot/ipam/forms.py +6 -3
- nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
- nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
- nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/navigation.py +3 -2
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/tables.py +19 -6
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
- nautobot/ipam/templates/ipam/ipaddress.html +14 -0
- nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +13 -6
- nautobot/ipam/tests/test_filters.py +36 -1
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +44 -2
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +13 -14
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +53 -11
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +1 -1
- nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
- nautobot/project-static/nautobot-icons/elements.svg +1 -1
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +1 -1
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +1 -1
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +1 -1
- nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
- nautobot/project-static/nautobot-icons/star.svg +1 -1
- nautobot/tenancy/api/serializers.py +1 -0
- nautobot/tenancy/api/views.py +2 -1
- nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
- nautobot/tenancy/navigation.py +3 -1
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +112 -30
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/users/templates/users/profile.html +45 -12
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/views.py +4 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/navigation.py +3 -2
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +45 -4
- nautobot/virtualization/views.py +4 -1
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +219 -0
- nautobot/vpn/filters.py +234 -0
- nautobot/vpn/forms.py +487 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +535 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +383 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +336 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +293 -0
- nautobot/vpn/tests/test_models.py +147 -0
- nautobot/vpn/tests/test_views.py +300 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +495 -0
- nautobot/wireless/navigation.py +3 -2
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/template_content.py +0 -42
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
nautobot/dcim/views.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
2
|
from copy import deepcopy
|
|
3
|
+
from functools import partial
|
|
3
4
|
import logging
|
|
4
5
|
import re
|
|
5
6
|
import uuid
|
|
@@ -20,7 +21,7 @@ from django.template import Context
|
|
|
20
21
|
from django.template.loader import render_to_string
|
|
21
22
|
from django.urls import reverse
|
|
22
23
|
from django.utils.encoding import iri_to_uri
|
|
23
|
-
from django.utils.html import format_html, mark_safe
|
|
24
|
+
from django.utils.html import format_html, format_html_join, mark_safe
|
|
24
25
|
from django.utils.http import url_has_allowed_host_and_scheme, urlencode
|
|
25
26
|
from django.views.generic import View
|
|
26
27
|
from django_tables2 import RequestConfig
|
|
@@ -35,10 +36,10 @@ from nautobot.core.exceptions import AbortTransaction
|
|
|
35
36
|
from nautobot.core.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields
|
|
36
37
|
from nautobot.core.models.querysets import count_related
|
|
37
38
|
from nautobot.core.templatetags import helpers
|
|
38
|
-
from nautobot.core.templatetags.helpers import has_perms
|
|
39
|
+
from nautobot.core.templatetags.helpers import bettertitle, has_perms
|
|
39
40
|
from nautobot.core.ui import object_detail
|
|
40
41
|
from nautobot.core.ui.breadcrumbs import (
|
|
41
|
-
|
|
42
|
+
AncestorsInstanceBreadcrumbItem,
|
|
42
43
|
BaseBreadcrumbItem,
|
|
43
44
|
Breadcrumbs,
|
|
44
45
|
context_object_attr,
|
|
@@ -76,7 +77,6 @@ from nautobot.core.views.utils import common_detail_view_context, get_obj_from_c
|
|
|
76
77
|
from nautobot.core.views.viewsets import NautobotUIViewSet
|
|
77
78
|
from nautobot.dcim.choices import LocationDataToContactActionChoices
|
|
78
79
|
from nautobot.dcim.forms import LocationMigrateDataToContactForm
|
|
79
|
-
from nautobot.dcim.ui import RackBreadcrumbs
|
|
80
80
|
from nautobot.dcim.utils import get_all_network_driver_mappings, render_software_version_and_image_files
|
|
81
81
|
from nautobot.extras.models import ConfigContext, Contact, ContactAssociation, Role, Status, Team
|
|
82
82
|
from nautobot.extras.tables import DynamicGroupTable, ImageAttachmentTable
|
|
@@ -91,6 +91,7 @@ from nautobot.ipam.tables import (
|
|
|
91
91
|
from nautobot.ipam.utils import render_ip_with_nat
|
|
92
92
|
from nautobot.virtualization.models import VirtualMachine
|
|
93
93
|
from nautobot.virtualization.tables import ClusterTable, VirtualMachineTable
|
|
94
|
+
from nautobot.vpn.tables import VPNTunnelEndpointTable
|
|
94
95
|
from nautobot.wireless.forms import ControllerManagedDeviceGroupWirelessNetworkFormSet
|
|
95
96
|
from nautobot.wireless.tables import (
|
|
96
97
|
BaseControllerManagedDeviceGroupWirelessNetworkAssignmentTable,
|
|
@@ -247,7 +248,14 @@ class LocationTypeUIViewSet(NautobotUIViewSet):
|
|
|
247
248
|
form_class = forms.LocationTypeForm
|
|
248
249
|
bulk_update_form_class = forms.LocationTypeBulkEditForm
|
|
249
250
|
serializer_class = serializers.LocationSerializer
|
|
250
|
-
breadcrumbs =
|
|
251
|
+
breadcrumbs = Breadcrumbs(
|
|
252
|
+
items={
|
|
253
|
+
"detail": [
|
|
254
|
+
ModelBreadcrumbItem(),
|
|
255
|
+
AncestorsInstanceBreadcrumbItem(),
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
)
|
|
251
259
|
|
|
252
260
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
253
261
|
panels=(
|
|
@@ -278,6 +286,106 @@ class LocationTypeUIViewSet(NautobotUIViewSet):
|
|
|
278
286
|
#
|
|
279
287
|
|
|
280
288
|
|
|
289
|
+
class LocationGeographicalInfoFieldsPanel(object_detail.ObjectFieldsPanel):
|
|
290
|
+
def get_data(self, context):
|
|
291
|
+
data = super().get_data(context)
|
|
292
|
+
obj = get_obj_from_context(context, self.context_object_key)
|
|
293
|
+
|
|
294
|
+
if obj and obj.latitude and obj.longitude:
|
|
295
|
+
data["GPS Coordinates"] = f"{obj.latitude}, {obj.longitude}"
|
|
296
|
+
else:
|
|
297
|
+
data["GPS Coordinates"] = None
|
|
298
|
+
|
|
299
|
+
return data
|
|
300
|
+
|
|
301
|
+
def render_value(self, key, value, context):
|
|
302
|
+
if key == "GPS Coordinates":
|
|
303
|
+
if value is not None:
|
|
304
|
+
return helpers.render_address(value)
|
|
305
|
+
return helpers.HTML_NONE
|
|
306
|
+
|
|
307
|
+
return super().render_value(key, value, context)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class LocationRackGroupsPanel(object_detail.Panel):
|
|
311
|
+
def render_rack_row(self, indent_px, url, name, count, elevation_url):
|
|
312
|
+
"""Render a single <tr> for a rack group or summary row."""
|
|
313
|
+
return format_html(
|
|
314
|
+
"""
|
|
315
|
+
<tr>
|
|
316
|
+
<td style="padding-left: {}px">
|
|
317
|
+
<span class="mdi mdi-folder-open"></span>
|
|
318
|
+
<a href="{}">{}</a>
|
|
319
|
+
</td>
|
|
320
|
+
<td>{}</td>
|
|
321
|
+
<td class="float-end d-print-none">
|
|
322
|
+
<a href="{}" class="btn btn-sm btn-primary" title="View elevations">
|
|
323
|
+
<span class="mdi mdi-server"></span>
|
|
324
|
+
</a>
|
|
325
|
+
</td>
|
|
326
|
+
</tr>
|
|
327
|
+
""",
|
|
328
|
+
indent_px,
|
|
329
|
+
url,
|
|
330
|
+
name,
|
|
331
|
+
count,
|
|
332
|
+
elevation_url,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def render_body_content(self, context):
|
|
336
|
+
"""Render the <tbody> content for the Rack Groups table."""
|
|
337
|
+
obj = get_obj_from_context(context)
|
|
338
|
+
if not obj:
|
|
339
|
+
return ""
|
|
340
|
+
|
|
341
|
+
rack_groups = context.get("rack_groups", [])
|
|
342
|
+
rack_count = context.get("rack_count", 0)
|
|
343
|
+
|
|
344
|
+
rows = []
|
|
345
|
+
|
|
346
|
+
# Render each rack group row
|
|
347
|
+
for rack_group in rack_groups:
|
|
348
|
+
rows.append(
|
|
349
|
+
self.render_rack_row(
|
|
350
|
+
getattr(rack_group, "tree_depth", 0) * 8,
|
|
351
|
+
rack_group.get_absolute_url(),
|
|
352
|
+
str(rack_group),
|
|
353
|
+
rack_group.rack_count,
|
|
354
|
+
f"{reverse('dcim:rack_elevation_list')}?rack_group={rack_group.pk}",
|
|
355
|
+
)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Add "All racks" row
|
|
359
|
+
rows.append(
|
|
360
|
+
self.render_rack_row(
|
|
361
|
+
10,
|
|
362
|
+
"#",
|
|
363
|
+
"All racks",
|
|
364
|
+
rack_count,
|
|
365
|
+
f"{reverse('dcim:rack_elevation_list')}?location={obj.pk}",
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
return format_html_join("", "{}", ((row,) for row in rows))
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class LocationImageAttachmentsTablePanel(object_detail.ObjectsTablePanel):
|
|
373
|
+
"""
|
|
374
|
+
ObjectsTablePanel with a custom _get_table_add_url() implementation.
|
|
375
|
+
|
|
376
|
+
Needed because the URL is `/dcim/devices/<pk>/images/add/`, not `extras/image-attachments/add?location=<pk>`.
|
|
377
|
+
"""
|
|
378
|
+
|
|
379
|
+
def _get_table_add_url(self, context):
|
|
380
|
+
obj = get_obj_from_context(context)
|
|
381
|
+
request = context["request"]
|
|
382
|
+
return_url = context.get("return_url", obj.get_absolute_url())
|
|
383
|
+
|
|
384
|
+
if not request.user.has_perms(["extras.add_imageattachment"]):
|
|
385
|
+
return None
|
|
386
|
+
return reverse("dcim:location_add_image", kwargs={"object_id": obj.pk}) + f"?return_url={return_url}"
|
|
387
|
+
|
|
388
|
+
|
|
281
389
|
class LocationUIViewSet(NautobotUIViewSet):
|
|
282
390
|
# We are only accessing the tree fields from the list view, where `with_tree_fields` is called dynamically
|
|
283
391
|
# depending on whether the hierarchy is shown in the UI (note that `parent` itself is a normal foreign key, not a
|
|
@@ -291,72 +399,132 @@ class LocationUIViewSet(NautobotUIViewSet):
|
|
|
291
399
|
form_class = forms.LocationForm
|
|
292
400
|
bulk_update_form_class = forms.LocationBulkEditForm
|
|
293
401
|
serializer_class = serializers.LocationSerializer
|
|
294
|
-
breadcrumbs =
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# ensure it is only performed once rather than as a subquery for each of the different count stats.
|
|
301
|
-
related_locations = list(
|
|
302
|
-
instance.descendants(include_self=True).restrict(request.user, "view").values_list("pk", flat=True)
|
|
303
|
-
)
|
|
304
|
-
prefix_count_queryset = Prefix.objects.restrict(request.user, "view").filter(locations__in=related_locations)
|
|
305
|
-
vlan_count_queryset = VLAN.objects.restrict(request.user, "view").filter(locations__in=related_locations)
|
|
306
|
-
circuit_count_queryset = Circuit.objects.restrict(request.user, "view").filter(
|
|
307
|
-
circuit_terminations__location__in=related_locations
|
|
308
|
-
)
|
|
309
|
-
# When there is more than one location, the models that can be assigned to more then one location at the same
|
|
310
|
-
# time need to be queried with `distinct`. We are avoiding `distinct` when this is not the case, as it incurs
|
|
311
|
-
# a performance penalty.
|
|
312
|
-
if len(related_locations) > 1:
|
|
313
|
-
prefix_count_queryset = prefix_count_queryset.distinct()
|
|
314
|
-
vlan_count_queryset = vlan_count_queryset.distinct()
|
|
315
|
-
circuit_count_queryset = circuit_count_queryset.distinct()
|
|
316
|
-
stats = {
|
|
317
|
-
"prefix_count": prefix_count_queryset.count(),
|
|
318
|
-
"vlan_count": vlan_count_queryset.count(),
|
|
319
|
-
"circuit_count": circuit_count_queryset.count(),
|
|
320
|
-
"rack_count": Rack.objects.restrict(request.user, "view").filter(location__in=related_locations).count(),
|
|
321
|
-
"device_count": Device.objects.restrict(request.user, "view")
|
|
322
|
-
.filter(location__in=related_locations)
|
|
323
|
-
.count(),
|
|
324
|
-
"vm_count": VirtualMachine.objects.restrict(request.user, "view")
|
|
325
|
-
.filter(cluster__location__in=related_locations)
|
|
326
|
-
.count(),
|
|
402
|
+
breadcrumbs = Breadcrumbs(
|
|
403
|
+
items={
|
|
404
|
+
"detail": [
|
|
405
|
+
ModelBreadcrumbItem(),
|
|
406
|
+
AncestorsInstanceBreadcrumbItem(),
|
|
407
|
+
]
|
|
327
408
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
409
|
+
)
|
|
410
|
+
view_titles = Titles(titles={"detail": "{{ object.name }}"})
|
|
411
|
+
|
|
412
|
+
object_detail_content = object_detail.ObjectDetailContent(
|
|
413
|
+
panels=(
|
|
414
|
+
object_detail.ObjectFieldsPanel(
|
|
415
|
+
weight=100,
|
|
416
|
+
section=SectionChoices.LEFT_HALF,
|
|
417
|
+
fields=[
|
|
418
|
+
"location_type",
|
|
419
|
+
"status",
|
|
420
|
+
"parent",
|
|
421
|
+
"tenant",
|
|
422
|
+
"facility",
|
|
423
|
+
"asn",
|
|
424
|
+
"time_zone",
|
|
425
|
+
"description",
|
|
426
|
+
],
|
|
427
|
+
value_transforms={
|
|
428
|
+
"location_type": [partial(helpers.hyperlinked_object, field="name")],
|
|
429
|
+
"time_zone": [helpers.format_timezone],
|
|
430
|
+
},
|
|
431
|
+
key_transforms={
|
|
432
|
+
"asn": "AS Number",
|
|
433
|
+
},
|
|
434
|
+
),
|
|
435
|
+
LocationGeographicalInfoFieldsPanel(
|
|
436
|
+
weight=110,
|
|
437
|
+
section=SectionChoices.LEFT_HALF,
|
|
438
|
+
label="Geographical Info",
|
|
439
|
+
fields=[
|
|
440
|
+
"physical_address",
|
|
441
|
+
"shipping_address",
|
|
442
|
+
],
|
|
443
|
+
value_transforms={
|
|
444
|
+
"physical_address": [helpers.render_address],
|
|
445
|
+
"shipping_address": [helpers.render_address],
|
|
446
|
+
},
|
|
447
|
+
),
|
|
448
|
+
object_detail.ObjectFieldsPanel(
|
|
449
|
+
weight=120,
|
|
450
|
+
section=SectionChoices.LEFT_HALF,
|
|
451
|
+
label="Contact Info",
|
|
452
|
+
fields=["contact_name", "contact_phone", "contact_email"],
|
|
453
|
+
value_transforms={
|
|
454
|
+
"contact_phone": [helpers.hyperlinked_phone_number],
|
|
455
|
+
"contact_email": [helpers.hyperlinked_email],
|
|
456
|
+
},
|
|
457
|
+
footer_content_template_path="dcim/footer_convert_to_contact_or_team_record.html",
|
|
458
|
+
),
|
|
459
|
+
object_detail.StatsPanel(
|
|
460
|
+
weight=100,
|
|
461
|
+
label="Stats",
|
|
462
|
+
section=SectionChoices.RIGHT_HALF,
|
|
463
|
+
filter_name="location",
|
|
464
|
+
related_models=[
|
|
465
|
+
Rack,
|
|
466
|
+
Device,
|
|
467
|
+
Prefix,
|
|
468
|
+
VLAN,
|
|
469
|
+
(Circuit, "circuit_terminations__location__in"),
|
|
470
|
+
(VirtualMachine, "cluster__location__in"),
|
|
471
|
+
],
|
|
472
|
+
),
|
|
473
|
+
LocationRackGroupsPanel(
|
|
474
|
+
label="Rack Groups",
|
|
475
|
+
section=SectionChoices.RIGHT_HALF,
|
|
476
|
+
weight=200,
|
|
477
|
+
body_wrapper_template_path="components/panel/body_wrapper_generic_table.html",
|
|
478
|
+
),
|
|
479
|
+
LocationImageAttachmentsTablePanel(
|
|
480
|
+
weight=300,
|
|
481
|
+
section=SectionChoices.RIGHT_HALF,
|
|
482
|
+
table_title="Images",
|
|
483
|
+
table_class=ImageAttachmentTable,
|
|
484
|
+
table_attribute="images",
|
|
485
|
+
related_field_name="location",
|
|
486
|
+
show_table_config_button=False,
|
|
487
|
+
enable_related_link=False,
|
|
488
|
+
),
|
|
489
|
+
object_detail.ObjectsTablePanel(
|
|
490
|
+
section=SectionChoices.FULL_WIDTH,
|
|
491
|
+
weight=100,
|
|
492
|
+
table_title="Children",
|
|
493
|
+
table_class=tables.LocationTable,
|
|
494
|
+
table_attribute="children",
|
|
495
|
+
related_field_name="parent",
|
|
496
|
+
order_by_fields=["name"],
|
|
497
|
+
hide_hierarchy_ui=True,
|
|
498
|
+
),
|
|
342
499
|
)
|
|
500
|
+
)
|
|
343
501
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"paginator_class": EnhancedPaginator,
|
|
347
|
-
"per_page": get_paginate_count(request),
|
|
348
|
-
}
|
|
349
|
-
RequestConfig(request, paginate).configure(children_table)
|
|
502
|
+
def get_extra_context(self, request, instance):
|
|
503
|
+
context = super().get_extra_context(request, instance)
|
|
350
504
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
505
|
+
if self.action == "retrieve":
|
|
506
|
+
# This query can get really expensive when there are big location trees in the DB. By casting it to a list we
|
|
507
|
+
# ensure it is only performed once rather than as a subquery for each of the different count stats.
|
|
508
|
+
related_locations = list(
|
|
509
|
+
instance.descendants(include_self=True).restrict(request.user, "view").values_list("pk", flat=True)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
rack_groups = (
|
|
513
|
+
RackGroup.objects.annotate(rack_count=count_related(Rack, "rack_group"))
|
|
514
|
+
.restrict(request.user, "view")
|
|
515
|
+
.filter(location__in=related_locations)
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
context.update(
|
|
519
|
+
{
|
|
520
|
+
"rack_groups": rack_groups,
|
|
521
|
+
"rack_count": Rack.objects.restrict(request.user, "view")
|
|
522
|
+
.filter(location__in=related_locations)
|
|
523
|
+
.count(),
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return context
|
|
360
528
|
|
|
361
529
|
|
|
362
530
|
class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
@@ -506,6 +674,14 @@ class RackGroupUIViewSet(NautobotUIViewSet):
|
|
|
506
674
|
serializer_class = serializers.RackGroupSerializer
|
|
507
675
|
table_class = tables.RackGroupTable
|
|
508
676
|
queryset = RackGroup.objects.all()
|
|
677
|
+
breadcrumbs = Breadcrumbs(
|
|
678
|
+
items={
|
|
679
|
+
"detail": [
|
|
680
|
+
ModelBreadcrumbItem(),
|
|
681
|
+
AncestorsInstanceBreadcrumbItem(),
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
)
|
|
509
685
|
|
|
510
686
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
511
687
|
panels=[
|
|
@@ -565,7 +741,26 @@ class RackUIViewSet(NautobotUIViewSet):
|
|
|
565
741
|
serializer_class = serializers.RackSerializer
|
|
566
742
|
table_class = tables.RackDetailTable
|
|
567
743
|
queryset = Rack.objects.select_related("location", "tenant__tenant_group", "rack_group", "role")
|
|
568
|
-
breadcrumbs =
|
|
744
|
+
breadcrumbs = Breadcrumbs(
|
|
745
|
+
items={
|
|
746
|
+
"detail": [
|
|
747
|
+
ModelBreadcrumbItem(),
|
|
748
|
+
AncestorsInstanceBreadcrumbItem(
|
|
749
|
+
instance=context_object_attr("location"),
|
|
750
|
+
ancestor_item=lambda ancestor: InstanceParentBreadcrumbItem(parent_key="location", parent=ancestor),
|
|
751
|
+
include_self=True,
|
|
752
|
+
),
|
|
753
|
+
AncestorsInstanceBreadcrumbItem(
|
|
754
|
+
instance=context_object_attr("rack_group"),
|
|
755
|
+
ancestor_item=lambda ancestor: InstanceParentBreadcrumbItem(
|
|
756
|
+
parent_key="rack_group", parent=ancestor
|
|
757
|
+
),
|
|
758
|
+
include_self=True,
|
|
759
|
+
should_render=context_object_attr("rack_group"),
|
|
760
|
+
),
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
)
|
|
569
764
|
|
|
570
765
|
def get_extra_context(self, request, instance):
|
|
571
766
|
context = super().get_extra_context(request, instance)
|
|
@@ -611,9 +806,6 @@ class RackElevationListView(generic.ObjectListView):
|
|
|
611
806
|
action_buttons = []
|
|
612
807
|
template_name = "dcim/rack_elevation_list.html"
|
|
613
808
|
view_titles = Titles(titles={"list": "Rack Elevation"})
|
|
614
|
-
breadcrumbs = Breadcrumbs(
|
|
615
|
-
items={"list": [ViewNameBreadcrumbItem(view_name="dcim:rack_elevation_list", label="Rack Elevation")]}
|
|
616
|
-
)
|
|
617
809
|
|
|
618
810
|
def extra_context(self):
|
|
619
811
|
racks = self.queryset
|
|
@@ -801,139 +993,6 @@ def bulk_cable_termination_footer_buttons(form_id: str, model):
|
|
|
801
993
|
]
|
|
802
994
|
|
|
803
995
|
|
|
804
|
-
# --- Tab Configuration ---
|
|
805
|
-
TAB_CONFIGS = [
|
|
806
|
-
(
|
|
807
|
-
100,
|
|
808
|
-
"interfaces",
|
|
809
|
-
"Interfaces",
|
|
810
|
-
"dcim:devicetype_interfaces",
|
|
811
|
-
"interface_templates",
|
|
812
|
-
tables.InterfaceTemplateTable,
|
|
813
|
-
InterfaceTemplate,
|
|
814
|
-
),
|
|
815
|
-
(
|
|
816
|
-
200,
|
|
817
|
-
"frontports",
|
|
818
|
-
"Front Ports",
|
|
819
|
-
"dcim:devicetype_frontports",
|
|
820
|
-
"front_port_templates",
|
|
821
|
-
tables.FrontPortTemplateTable,
|
|
822
|
-
FrontPortTemplate,
|
|
823
|
-
),
|
|
824
|
-
(
|
|
825
|
-
300,
|
|
826
|
-
"rearports",
|
|
827
|
-
"Rear Ports",
|
|
828
|
-
"dcim:devicetype_rearports",
|
|
829
|
-
"rear_port_templates",
|
|
830
|
-
tables.RearPortTemplateTable,
|
|
831
|
-
RearPortTemplate,
|
|
832
|
-
),
|
|
833
|
-
(
|
|
834
|
-
400,
|
|
835
|
-
"consoleports",
|
|
836
|
-
"Console Ports",
|
|
837
|
-
"dcim:devicetype_consoleports",
|
|
838
|
-
"console_port_templates",
|
|
839
|
-
tables.ConsolePortTemplateTable,
|
|
840
|
-
ConsolePortTemplate,
|
|
841
|
-
),
|
|
842
|
-
(
|
|
843
|
-
500,
|
|
844
|
-
"consoleserverports",
|
|
845
|
-
"Console Server Ports",
|
|
846
|
-
"dcim:devicetype_consoleserverports",
|
|
847
|
-
"console_server_port_templates",
|
|
848
|
-
tables.ConsoleServerPortTemplateTable,
|
|
849
|
-
ConsoleServerPortTemplate,
|
|
850
|
-
),
|
|
851
|
-
(
|
|
852
|
-
600,
|
|
853
|
-
"powerports",
|
|
854
|
-
"Power Ports",
|
|
855
|
-
"dcim:devicetype_powerports",
|
|
856
|
-
"power_port_templates",
|
|
857
|
-
tables.PowerPortTemplateTable,
|
|
858
|
-
PowerPortTemplate,
|
|
859
|
-
),
|
|
860
|
-
(
|
|
861
|
-
700,
|
|
862
|
-
"poweroutlets",
|
|
863
|
-
"Power Outlets",
|
|
864
|
-
"dcim:devicetype_poweroutlets",
|
|
865
|
-
"power_outlet_templates",
|
|
866
|
-
tables.PowerOutletTemplateTable,
|
|
867
|
-
PowerOutletTemplate,
|
|
868
|
-
),
|
|
869
|
-
(
|
|
870
|
-
800,
|
|
871
|
-
"devicebays",
|
|
872
|
-
"Device Bays",
|
|
873
|
-
"dcim:devicetype_devicebays",
|
|
874
|
-
"device_bay_templates",
|
|
875
|
-
tables.DeviceBayTemplateTable,
|
|
876
|
-
DeviceBayTemplate,
|
|
877
|
-
),
|
|
878
|
-
(
|
|
879
|
-
900,
|
|
880
|
-
"modulebays",
|
|
881
|
-
"Module Bays",
|
|
882
|
-
"dcim:devicetype_modulebays",
|
|
883
|
-
"module_bay_templates",
|
|
884
|
-
tables.ModuleBayTemplateTable,
|
|
885
|
-
ModuleBayTemplate,
|
|
886
|
-
),
|
|
887
|
-
]
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
# --- Add Components Button Config ---
|
|
891
|
-
ADD_COMPONENTS_CONFIG = [
|
|
892
|
-
(100, "dcim:consoleporttemplate_add", "Console Ports", "mdi-console", ["dcim.add_consoleporttemplate"]),
|
|
893
|
-
(
|
|
894
|
-
200,
|
|
895
|
-
"dcim:consoleserverporttemplate_add",
|
|
896
|
-
"Console Server Ports",
|
|
897
|
-
"mdi-console-network-outline",
|
|
898
|
-
["dcim.add_consoleserverporttemplate"],
|
|
899
|
-
),
|
|
900
|
-
(300, "dcim:powerporttemplate_add", "Power Ports", "mdi-power-plug-outline", ["dcim.add_powerporttemplate"]),
|
|
901
|
-
(400, "dcim:poweroutlettemplate_add", "Power Outlets", "mdi-power-socket", ["dcim.add_poweroutlettemplate"]),
|
|
902
|
-
(500, "dcim:interfacetemplate_add", "Interfaces", "mdi-ethernet", ["dcim.add_interfacetemplate"]),
|
|
903
|
-
(600, "dcim:frontporttemplate_add", "Front Ports", "mdi-square-rounded-outline", ["dcim.add_frontporttemplate"]),
|
|
904
|
-
(700, "dcim:rearporttemplate_add", "Rear Ports", "mdi-square-rounded-outline", ["dcim.add_rearporttemplate"]),
|
|
905
|
-
(800, "dcim:devicebaytemplate_add", "Device Bays", "mdi-circle-outline", ["dcim.add_devicebaytemplate"]),
|
|
906
|
-
(900, "dcim:modulebaytemplate_add", "Module Bays", "mdi-tray", ["dcim.add_modulebaytemplate"]),
|
|
907
|
-
]
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
def make_bulk_tab(weight, tab_name, label, url_name, related_attr, table_class, model):
|
|
911
|
-
"""Build a bulk-enabled tab."""
|
|
912
|
-
form_id = f"{tab_name}template_form"
|
|
913
|
-
return object_detail.DistinctViewTab(
|
|
914
|
-
weight=weight,
|
|
915
|
-
tab_id=tab_name,
|
|
916
|
-
label=label,
|
|
917
|
-
url_name=url_name,
|
|
918
|
-
related_object_attribute=related_attr,
|
|
919
|
-
hide_if_empty=True,
|
|
920
|
-
panels=(
|
|
921
|
-
object_detail.ObjectsTablePanel(
|
|
922
|
-
section=SectionChoices.FULL_WIDTH,
|
|
923
|
-
weight=100,
|
|
924
|
-
table_title=label,
|
|
925
|
-
table_class=table_class,
|
|
926
|
-
table_filter="device_type",
|
|
927
|
-
tab_id=tab_name,
|
|
928
|
-
enable_bulk_actions=True,
|
|
929
|
-
form_id=form_id,
|
|
930
|
-
footer_buttons=bulk_footer_buttons(form_id=form_id, model=model),
|
|
931
|
-
include_paginator=True,
|
|
932
|
-
),
|
|
933
|
-
),
|
|
934
|
-
)
|
|
935
|
-
|
|
936
|
-
|
|
937
996
|
# --- DeviceType UI ViewSet ---
|
|
938
997
|
class DeviceTypeUIViewSet(NautobotUIViewSet):
|
|
939
998
|
bulk_update_form_class = forms.DeviceTypeBulkEditForm
|
|
@@ -980,7 +1039,242 @@ class DeviceTypeUIViewSet(NautobotUIViewSet):
|
|
|
980
1039
|
exclude_columns=["actions", "tags"],
|
|
981
1040
|
),
|
|
982
1041
|
),
|
|
983
|
-
extra_tabs=
|
|
1042
|
+
extra_tabs=(
|
|
1043
|
+
object_detail.DistinctViewTab(
|
|
1044
|
+
weight=100,
|
|
1045
|
+
tab_id="interfaces",
|
|
1046
|
+
label="Interfaces",
|
|
1047
|
+
url_name="dcim:devicetype_interfaces",
|
|
1048
|
+
related_object_attribute="interface_templates",
|
|
1049
|
+
hide_if_empty=True,
|
|
1050
|
+
panels=(
|
|
1051
|
+
object_detail.ObjectsTablePanel(
|
|
1052
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1053
|
+
weight=100,
|
|
1054
|
+
table_title="Interfaces",
|
|
1055
|
+
table_class=tables.InterfaceTemplateTable,
|
|
1056
|
+
table_filter="device_type",
|
|
1057
|
+
tab_id="interfaces",
|
|
1058
|
+
enable_bulk_actions=True,
|
|
1059
|
+
form_id="interfacestemplate_form",
|
|
1060
|
+
footer_buttons=bulk_footer_buttons(
|
|
1061
|
+
form_id="interfacestemplate_form",
|
|
1062
|
+
model=InterfaceTemplate,
|
|
1063
|
+
),
|
|
1064
|
+
include_paginator=True,
|
|
1065
|
+
enable_related_link=False,
|
|
1066
|
+
),
|
|
1067
|
+
),
|
|
1068
|
+
),
|
|
1069
|
+
object_detail.DistinctViewTab(
|
|
1070
|
+
weight=200,
|
|
1071
|
+
tab_id="frontports",
|
|
1072
|
+
label="Front Ports",
|
|
1073
|
+
url_name="dcim:devicetype_frontports",
|
|
1074
|
+
related_object_attribute="front_port_templates",
|
|
1075
|
+
hide_if_empty=True,
|
|
1076
|
+
panels=(
|
|
1077
|
+
object_detail.ObjectsTablePanel(
|
|
1078
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1079
|
+
weight=100,
|
|
1080
|
+
table_title="Front Ports",
|
|
1081
|
+
table_class=tables.FrontPortTemplateTable,
|
|
1082
|
+
table_filter="device_type",
|
|
1083
|
+
tab_id="frontports",
|
|
1084
|
+
enable_bulk_actions=True,
|
|
1085
|
+
form_id="frontportstemplate_form",
|
|
1086
|
+
footer_buttons=bulk_footer_buttons(
|
|
1087
|
+
form_id="frontportstemplate_form",
|
|
1088
|
+
model=FrontPortTemplate,
|
|
1089
|
+
),
|
|
1090
|
+
include_paginator=True,
|
|
1091
|
+
enable_related_link=False,
|
|
1092
|
+
),
|
|
1093
|
+
),
|
|
1094
|
+
),
|
|
1095
|
+
object_detail.DistinctViewTab(
|
|
1096
|
+
weight=300,
|
|
1097
|
+
tab_id="rearports",
|
|
1098
|
+
label="Rear Ports",
|
|
1099
|
+
url_name="dcim:devicetype_rearports",
|
|
1100
|
+
related_object_attribute="rear_port_templates",
|
|
1101
|
+
hide_if_empty=True,
|
|
1102
|
+
panels=(
|
|
1103
|
+
object_detail.ObjectsTablePanel(
|
|
1104
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1105
|
+
weight=100,
|
|
1106
|
+
table_title="Rear Ports",
|
|
1107
|
+
table_class=tables.RearPortTemplateTable,
|
|
1108
|
+
table_filter="device_type",
|
|
1109
|
+
tab_id="rearports",
|
|
1110
|
+
enable_bulk_actions=True,
|
|
1111
|
+
form_id="rearportstemplate_form",
|
|
1112
|
+
footer_buttons=bulk_footer_buttons(
|
|
1113
|
+
form_id="rearportstemplate_form",
|
|
1114
|
+
model=RearPortTemplate,
|
|
1115
|
+
),
|
|
1116
|
+
include_paginator=True,
|
|
1117
|
+
enable_related_link=False,
|
|
1118
|
+
),
|
|
1119
|
+
),
|
|
1120
|
+
),
|
|
1121
|
+
object_detail.DistinctViewTab(
|
|
1122
|
+
weight=400,
|
|
1123
|
+
tab_id="consoleports",
|
|
1124
|
+
label="Console Ports",
|
|
1125
|
+
url_name="dcim:devicetype_consoleports",
|
|
1126
|
+
related_object_attribute="console_port_templates",
|
|
1127
|
+
hide_if_empty=True,
|
|
1128
|
+
panels=(
|
|
1129
|
+
object_detail.ObjectsTablePanel(
|
|
1130
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1131
|
+
weight=100,
|
|
1132
|
+
table_title="Console Ports",
|
|
1133
|
+
table_class=tables.ConsolePortTemplateTable,
|
|
1134
|
+
table_filter="device_type",
|
|
1135
|
+
tab_id="consoleports",
|
|
1136
|
+
enable_bulk_actions=True,
|
|
1137
|
+
form_id="consoleportstemplate_form",
|
|
1138
|
+
footer_buttons=bulk_footer_buttons(
|
|
1139
|
+
form_id="consoleportstemplate_form",
|
|
1140
|
+
model=ConsolePortTemplate,
|
|
1141
|
+
),
|
|
1142
|
+
include_paginator=True,
|
|
1143
|
+
enable_related_link=False,
|
|
1144
|
+
),
|
|
1145
|
+
),
|
|
1146
|
+
),
|
|
1147
|
+
object_detail.DistinctViewTab(
|
|
1148
|
+
weight=500,
|
|
1149
|
+
tab_id="consoleserverports",
|
|
1150
|
+
label="Console Server Ports",
|
|
1151
|
+
url_name="dcim:devicetype_consoleserverports",
|
|
1152
|
+
related_object_attribute="console_server_port_templates",
|
|
1153
|
+
hide_if_empty=True,
|
|
1154
|
+
panels=(
|
|
1155
|
+
object_detail.ObjectsTablePanel(
|
|
1156
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1157
|
+
weight=100,
|
|
1158
|
+
table_title="Console Server Ports",
|
|
1159
|
+
table_class=tables.ConsoleServerPortTemplateTable,
|
|
1160
|
+
table_filter="device_type",
|
|
1161
|
+
tab_id="consoleserverports",
|
|
1162
|
+
enable_bulk_actions=True,
|
|
1163
|
+
form_id="consoleserverportstemplate_form",
|
|
1164
|
+
footer_buttons=bulk_footer_buttons(
|
|
1165
|
+
form_id="consoleserverportstemplate_form",
|
|
1166
|
+
model=ConsoleServerPortTemplate,
|
|
1167
|
+
),
|
|
1168
|
+
include_paginator=True,
|
|
1169
|
+
enable_related_link=False,
|
|
1170
|
+
),
|
|
1171
|
+
),
|
|
1172
|
+
),
|
|
1173
|
+
object_detail.DistinctViewTab(
|
|
1174
|
+
weight=600,
|
|
1175
|
+
tab_id="powerports",
|
|
1176
|
+
label="Power Ports",
|
|
1177
|
+
url_name="dcim:devicetype_powerports",
|
|
1178
|
+
related_object_attribute="power_port_templates",
|
|
1179
|
+
hide_if_empty=True,
|
|
1180
|
+
panels=(
|
|
1181
|
+
object_detail.ObjectsTablePanel(
|
|
1182
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1183
|
+
weight=100,
|
|
1184
|
+
table_title="Power Ports",
|
|
1185
|
+
table_class=tables.PowerPortTemplateTable,
|
|
1186
|
+
table_filter="device_type",
|
|
1187
|
+
tab_id="powerports",
|
|
1188
|
+
enable_bulk_actions=True,
|
|
1189
|
+
form_id="powerportstemplate_form",
|
|
1190
|
+
footer_buttons=bulk_footer_buttons(
|
|
1191
|
+
form_id="powerportstemplate_form",
|
|
1192
|
+
model=PowerPortTemplate,
|
|
1193
|
+
),
|
|
1194
|
+
include_paginator=True,
|
|
1195
|
+
enable_related_link=False,
|
|
1196
|
+
),
|
|
1197
|
+
),
|
|
1198
|
+
),
|
|
1199
|
+
object_detail.DistinctViewTab(
|
|
1200
|
+
weight=700,
|
|
1201
|
+
tab_id="poweroutlets",
|
|
1202
|
+
label="Power Outlets",
|
|
1203
|
+
url_name="dcim:devicetype_poweroutlets",
|
|
1204
|
+
related_object_attribute="power_outlet_templates",
|
|
1205
|
+
hide_if_empty=True,
|
|
1206
|
+
panels=(
|
|
1207
|
+
object_detail.ObjectsTablePanel(
|
|
1208
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1209
|
+
weight=100,
|
|
1210
|
+
table_title="Power Outlets",
|
|
1211
|
+
table_class=tables.PowerOutletTemplateTable,
|
|
1212
|
+
table_filter="device_type",
|
|
1213
|
+
tab_id="poweroutlets",
|
|
1214
|
+
enable_bulk_actions=True,
|
|
1215
|
+
form_id="poweroutletstemplate_form",
|
|
1216
|
+
footer_buttons=bulk_footer_buttons(
|
|
1217
|
+
form_id="poweroutletstemplate_form",
|
|
1218
|
+
model=PowerOutletTemplate,
|
|
1219
|
+
),
|
|
1220
|
+
include_paginator=True,
|
|
1221
|
+
enable_related_link=False,
|
|
1222
|
+
),
|
|
1223
|
+
),
|
|
1224
|
+
),
|
|
1225
|
+
object_detail.DistinctViewTab(
|
|
1226
|
+
weight=800,
|
|
1227
|
+
tab_id="devicebays",
|
|
1228
|
+
label="Device Bays",
|
|
1229
|
+
url_name="dcim:devicetype_devicebays",
|
|
1230
|
+
related_object_attribute="device_bay_templates",
|
|
1231
|
+
hide_if_empty=True,
|
|
1232
|
+
panels=(
|
|
1233
|
+
object_detail.ObjectsTablePanel(
|
|
1234
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1235
|
+
weight=100,
|
|
1236
|
+
table_title="Device Bays",
|
|
1237
|
+
table_class=tables.DeviceBayTemplateTable,
|
|
1238
|
+
table_filter="device_type",
|
|
1239
|
+
tab_id="devicebays",
|
|
1240
|
+
enable_bulk_actions=True,
|
|
1241
|
+
form_id="devicebaystemplate_form",
|
|
1242
|
+
footer_buttons=bulk_footer_buttons(
|
|
1243
|
+
form_id="devicebaystemplate_form",
|
|
1244
|
+
model=DeviceBayTemplate,
|
|
1245
|
+
),
|
|
1246
|
+
include_paginator=True,
|
|
1247
|
+
enable_related_link=False,
|
|
1248
|
+
),
|
|
1249
|
+
),
|
|
1250
|
+
),
|
|
1251
|
+
object_detail.DistinctViewTab(
|
|
1252
|
+
weight=900,
|
|
1253
|
+
tab_id="modulebays",
|
|
1254
|
+
label="Module Bays",
|
|
1255
|
+
url_name="dcim:devicetype_modulebays",
|
|
1256
|
+
related_object_attribute="module_bay_templates",
|
|
1257
|
+
hide_if_empty=True,
|
|
1258
|
+
panels=(
|
|
1259
|
+
object_detail.ObjectsTablePanel(
|
|
1260
|
+
section=SectionChoices.FULL_WIDTH,
|
|
1261
|
+
weight=100,
|
|
1262
|
+
table_title="Module Bays",
|
|
1263
|
+
table_class=tables.ModuleBayTemplateTable,
|
|
1264
|
+
table_filter="device_type",
|
|
1265
|
+
tab_id="modulebays",
|
|
1266
|
+
enable_bulk_actions=True,
|
|
1267
|
+
form_id="modulebaystemplate_form",
|
|
1268
|
+
footer_buttons=bulk_footer_buttons(
|
|
1269
|
+
form_id="modulebaystemplate_form",
|
|
1270
|
+
model=ModuleBayTemplate,
|
|
1271
|
+
),
|
|
1272
|
+
include_paginator=True,
|
|
1273
|
+
enable_related_link=False,
|
|
1274
|
+
),
|
|
1275
|
+
),
|
|
1276
|
+
),
|
|
1277
|
+
),
|
|
984
1278
|
extra_buttons=(
|
|
985
1279
|
object_detail.DropdownButton(
|
|
986
1280
|
weight=100,
|
|
@@ -989,16 +1283,70 @@ class DeviceTypeUIViewSet(NautobotUIViewSet):
|
|
|
989
1283
|
attributes={"id": "device-type-add-components-button"},
|
|
990
1284
|
icon="mdi-plus-thick",
|
|
991
1285
|
required_permissions=["dcim.change_devicetype"],
|
|
992
|
-
children=
|
|
1286
|
+
children=(
|
|
993
1287
|
object_detail.Button(
|
|
994
|
-
weight=
|
|
995
|
-
link_name=
|
|
996
|
-
label=
|
|
997
|
-
icon=
|
|
998
|
-
required_permissions=
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1288
|
+
weight=100,
|
|
1289
|
+
link_name="dcim:devicetype_consoleporttemplate_add",
|
|
1290
|
+
label="Console Ports",
|
|
1291
|
+
icon="mdi-console",
|
|
1292
|
+
required_permissions=["dcim.add_consoleporttemplate"],
|
|
1293
|
+
),
|
|
1294
|
+
object_detail.Button(
|
|
1295
|
+
weight=200,
|
|
1296
|
+
link_name="dcim:devicetype_consoleserverporttemplate_add",
|
|
1297
|
+
label="Console Server Ports",
|
|
1298
|
+
icon="mdi-console-network-outline",
|
|
1299
|
+
required_permissions=["dcim.add_consoleserverporttemplate"],
|
|
1300
|
+
),
|
|
1301
|
+
object_detail.Button(
|
|
1302
|
+
weight=300,
|
|
1303
|
+
link_name="dcim:devicetype_powerporttemplate_add",
|
|
1304
|
+
label="Power Ports",
|
|
1305
|
+
icon="mdi-power-plug-outline",
|
|
1306
|
+
required_permissions=["dcim.add_powerporttemplate"],
|
|
1307
|
+
),
|
|
1308
|
+
object_detail.Button(
|
|
1309
|
+
weight=400,
|
|
1310
|
+
link_name="dcim:devicetype_poweroutlettemplate_add",
|
|
1311
|
+
label="Power Outlets",
|
|
1312
|
+
icon="mdi-power-socket",
|
|
1313
|
+
required_permissions=["dcim.add_poweroutlettemplate"],
|
|
1314
|
+
),
|
|
1315
|
+
object_detail.Button(
|
|
1316
|
+
weight=500,
|
|
1317
|
+
link_name="dcim:devicetype_interfacetemplate_add",
|
|
1318
|
+
label="Interfaces",
|
|
1319
|
+
icon="mdi-ethernet",
|
|
1320
|
+
required_permissions=["dcim.add_interfacetemplate"],
|
|
1321
|
+
),
|
|
1322
|
+
object_detail.Button(
|
|
1323
|
+
weight=600,
|
|
1324
|
+
link_name="dcim:devicetype_frontporttemplate_add",
|
|
1325
|
+
label="Front Ports",
|
|
1326
|
+
icon="mdi-square-rounded-outline",
|
|
1327
|
+
required_permissions=["dcim.add_frontporttemplate"],
|
|
1328
|
+
),
|
|
1329
|
+
object_detail.Button(
|
|
1330
|
+
weight=700,
|
|
1331
|
+
link_name="dcim:devicetype_rearporttemplate_add",
|
|
1332
|
+
label="Rear Ports",
|
|
1333
|
+
icon="mdi-square-rounded-outline",
|
|
1334
|
+
required_permissions=["dcim.add_rearporttemplate"],
|
|
1335
|
+
),
|
|
1336
|
+
object_detail.Button(
|
|
1337
|
+
weight=800,
|
|
1338
|
+
link_name="dcim:devicetype_devicebaytemplate_add",
|
|
1339
|
+
label="Device Bays",
|
|
1340
|
+
icon="mdi-circle-outline",
|
|
1341
|
+
required_permissions=["dcim.add_devicebaytemplate"],
|
|
1342
|
+
),
|
|
1343
|
+
object_detail.Button(
|
|
1344
|
+
weight=900,
|
|
1345
|
+
link_name="dcim:devicetype_modulebaytemplate_add",
|
|
1346
|
+
label="Module Bays",
|
|
1347
|
+
icon="mdi-tray",
|
|
1348
|
+
required_permissions=["dcim.add_modulebaytemplate"],
|
|
1349
|
+
),
|
|
1002
1350
|
),
|
|
1003
1351
|
),
|
|
1004
1352
|
),
|
|
@@ -1138,6 +1486,7 @@ class ModuleTypeUIViewSet(
|
|
|
1138
1486
|
ObjectDestroyViewMixin,
|
|
1139
1487
|
ObjectBulkDestroyViewMixin,
|
|
1140
1488
|
ObjectBulkUpdateViewMixin,
|
|
1489
|
+
# ObjectDataComplianceViewMixin, # TODO: enable once converted to UI framework
|
|
1141
1490
|
ObjectChangeLogViewMixin,
|
|
1142
1491
|
ObjectNotesViewMixin,
|
|
1143
1492
|
):
|
|
@@ -1186,65 +1535,68 @@ class ModuleTypeUIViewSet(
|
|
|
1186
1535
|
return super().get_required_permission()
|
|
1187
1536
|
|
|
1188
1537
|
def get_extra_context(self, request, instance):
|
|
1189
|
-
|
|
1190
|
-
|
|
1538
|
+
context = super().get_extra_context(request, instance)
|
|
1539
|
+
if self.action == "retrieve":
|
|
1540
|
+
instance_count = Module.objects.restrict(request.user).filter(module_type=instance).count()
|
|
1191
1541
|
|
|
1192
|
-
|
|
1542
|
+
# Component tables
|
|
1543
|
+
consoleport_table = tables.ConsolePortTemplateTable(
|
|
1544
|
+
ConsolePortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1545
|
+
orderable=False,
|
|
1546
|
+
)
|
|
1547
|
+
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
|
|
1548
|
+
ConsoleServerPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1549
|
+
orderable=False,
|
|
1550
|
+
)
|
|
1551
|
+
powerport_table = tables.PowerPortTemplateTable(
|
|
1552
|
+
PowerPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1553
|
+
orderable=False,
|
|
1554
|
+
)
|
|
1555
|
+
poweroutlet_table = tables.PowerOutletTemplateTable(
|
|
1556
|
+
PowerOutletTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1557
|
+
orderable=False,
|
|
1558
|
+
)
|
|
1559
|
+
interface_table = tables.InterfaceTemplateTable(
|
|
1560
|
+
list(InterfaceTemplate.objects.restrict(request.user, "view").filter(module_type=instance)),
|
|
1561
|
+
orderable=False,
|
|
1562
|
+
)
|
|
1563
|
+
front_port_table = tables.FrontPortTemplateTable(
|
|
1564
|
+
FrontPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1565
|
+
orderable=False,
|
|
1566
|
+
)
|
|
1567
|
+
rear_port_table = tables.RearPortTemplateTable(
|
|
1568
|
+
RearPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1569
|
+
orderable=False,
|
|
1570
|
+
)
|
|
1571
|
+
modulebay_table = tables.ModuleBayTemplateTable(
|
|
1572
|
+
ModuleBayTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1573
|
+
orderable=False,
|
|
1574
|
+
)
|
|
1575
|
+
if request.user.has_perm("dcim.change_moduletype"):
|
|
1576
|
+
consoleport_table.columns.show("pk")
|
|
1577
|
+
consoleserverport_table.columns.show("pk")
|
|
1578
|
+
powerport_table.columns.show("pk")
|
|
1579
|
+
poweroutlet_table.columns.show("pk")
|
|
1580
|
+
interface_table.columns.show("pk")
|
|
1581
|
+
front_port_table.columns.show("pk")
|
|
1582
|
+
rear_port_table.columns.show("pk")
|
|
1583
|
+
modulebay_table.columns.show("pk")
|
|
1193
1584
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
poweroutlet_table = tables.PowerOutletTemplateTable(
|
|
1208
|
-
PowerOutletTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1209
|
-
orderable=False,
|
|
1210
|
-
)
|
|
1211
|
-
interface_table = tables.InterfaceTemplateTable(
|
|
1212
|
-
list(InterfaceTemplate.objects.restrict(request.user, "view").filter(module_type=instance)),
|
|
1213
|
-
orderable=False,
|
|
1214
|
-
)
|
|
1215
|
-
front_port_table = tables.FrontPortTemplateTable(
|
|
1216
|
-
FrontPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1217
|
-
orderable=False,
|
|
1218
|
-
)
|
|
1219
|
-
rear_port_table = tables.RearPortTemplateTable(
|
|
1220
|
-
RearPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1221
|
-
orderable=False,
|
|
1222
|
-
)
|
|
1223
|
-
modulebay_table = tables.ModuleBayTemplateTable(
|
|
1224
|
-
ModuleBayTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1225
|
-
orderable=False,
|
|
1226
|
-
)
|
|
1227
|
-
if request.user.has_perm("dcim.change_moduletype"):
|
|
1228
|
-
consoleport_table.columns.show("pk")
|
|
1229
|
-
consoleserverport_table.columns.show("pk")
|
|
1230
|
-
powerport_table.columns.show("pk")
|
|
1231
|
-
poweroutlet_table.columns.show("pk")
|
|
1232
|
-
interface_table.columns.show("pk")
|
|
1233
|
-
front_port_table.columns.show("pk")
|
|
1234
|
-
rear_port_table.columns.show("pk")
|
|
1235
|
-
modulebay_table.columns.show("pk")
|
|
1585
|
+
context.update(
|
|
1586
|
+
{
|
|
1587
|
+
"instance_count": instance_count,
|
|
1588
|
+
"consoleport_table": consoleport_table,
|
|
1589
|
+
"consoleserverport_table": consoleserverport_table,
|
|
1590
|
+
"powerport_table": powerport_table,
|
|
1591
|
+
"poweroutlet_table": poweroutlet_table,
|
|
1592
|
+
"interface_table": interface_table,
|
|
1593
|
+
"front_port_table": front_port_table,
|
|
1594
|
+
"rear_port_table": rear_port_table,
|
|
1595
|
+
"modulebay_table": modulebay_table,
|
|
1596
|
+
}
|
|
1597
|
+
)
|
|
1236
1598
|
|
|
1237
|
-
return
|
|
1238
|
-
"instance_count": instance_count,
|
|
1239
|
-
"consoleport_table": consoleport_table,
|
|
1240
|
-
"consoleserverport_table": consoleserverport_table,
|
|
1241
|
-
"powerport_table": powerport_table,
|
|
1242
|
-
"poweroutlet_table": poweroutlet_table,
|
|
1243
|
-
"interface_table": interface_table,
|
|
1244
|
-
"front_port_table": front_port_table,
|
|
1245
|
-
"rear_port_table": rear_port_table,
|
|
1246
|
-
"modulebay_table": modulebay_table,
|
|
1247
|
-
}
|
|
1599
|
+
return context
|
|
1248
1600
|
|
|
1249
1601
|
@action(
|
|
1250
1602
|
detail=False,
|
|
@@ -1947,32 +2299,43 @@ class DeviceComponentPageMixin:
|
|
|
1947
2299
|
- Console Port assigned to the device: Devices / <Device name and link to details> / Console Ports (dcim/devices/<id>/console-ports/) / <Console Port name>
|
|
1948
2300
|
"""
|
|
1949
2301
|
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2302
|
+
def __init_subclass__(cls, **kwargs):
|
|
2303
|
+
super().__init_subclass__(**kwargs)
|
|
2304
|
+
|
|
2305
|
+
device_breadcrumbs = [
|
|
2306
|
+
ModelBreadcrumbItem(model=Device, should_render=lambda c: c["object"].device is not None),
|
|
2307
|
+
InstanceBreadcrumbItem(
|
|
2308
|
+
instance=lambda c: c["object"].device, should_render=lambda c: c["object"].device is not None
|
|
2309
|
+
),
|
|
2310
|
+
]
|
|
2311
|
+
module_breadcrumbs = [
|
|
2312
|
+
ModelBreadcrumbItem(model=Module, should_render=lambda c: c["object"].device is None),
|
|
2313
|
+
InstanceBreadcrumbItem(
|
|
2314
|
+
instance=lambda c: c["object"].module, should_render=lambda c: c["object"].device is None
|
|
2315
|
+
),
|
|
2316
|
+
]
|
|
2317
|
+
if device_breadcrumb_url := getattr(cls, "device_breadcrumb_url", None):
|
|
2318
|
+
device_breadcrumbs.append(
|
|
1957
2319
|
ViewNameBreadcrumbItem(
|
|
1958
|
-
|
|
1959
|
-
should_render=lambda c: c["object"].device is not None
|
|
2320
|
+
view_name=device_breadcrumb_url,
|
|
2321
|
+
should_render=lambda c: c["object"].device is not None,
|
|
1960
2322
|
reverse_kwargs=lambda c: {"pk": c["object"].device.pk},
|
|
1961
|
-
label=lambda c: c["object"]._meta.verbose_name_plural,
|
|
1962
|
-
),
|
|
1963
|
-
ModelBreadcrumbItem(model=Module, should_render=lambda c: c["object"].device is None),
|
|
1964
|
-
InstanceBreadcrumbItem(
|
|
1965
|
-
instance=lambda c: c["object"].module, should_render=lambda c: c["object"].device is None
|
|
2323
|
+
label=lambda c: bettertitle(c["object"]._meta.verbose_name_plural),
|
|
1966
2324
|
),
|
|
2325
|
+
)
|
|
2326
|
+
|
|
2327
|
+
if module_breadcrumb_url := getattr(cls, "module_breadcrumb_url", None):
|
|
2328
|
+
module_breadcrumbs.append(
|
|
1967
2329
|
ViewNameBreadcrumbItem(
|
|
1968
|
-
|
|
1969
|
-
should_render=lambda c: c["object"].device is None
|
|
2330
|
+
view_name=module_breadcrumb_url,
|
|
2331
|
+
should_render=lambda c: c["object"].device is None,
|
|
1970
2332
|
reverse_kwargs=lambda c: {"pk": c["object"].module.pk},
|
|
1971
|
-
label=lambda c: c["object"]._meta.verbose_name_plural,
|
|
2333
|
+
label=lambda c: bettertitle(c["object"]._meta.verbose_name_plural),
|
|
1972
2334
|
),
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2335
|
+
)
|
|
2336
|
+
|
|
2337
|
+
cls.breadcrumbs = Breadcrumbs(items={"detail": [*device_breadcrumbs, *module_breadcrumbs]})
|
|
2338
|
+
|
|
1976
2339
|
view_titles = Titles(
|
|
1977
2340
|
titles={
|
|
1978
2341
|
"detail": "{% if object.device %}{{ object.device }}{% else %}{{ object.module.display }}{% endif %} / {{ object }}"
|
|
@@ -1995,7 +2358,11 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
1995
2358
|
items={
|
|
1996
2359
|
"detail": [
|
|
1997
2360
|
ModelBreadcrumbItem(model=Device),
|
|
1998
|
-
|
|
2361
|
+
AncestorsInstanceBreadcrumbItem(
|
|
2362
|
+
instance=context_object_attr("location"),
|
|
2363
|
+
include_self=True,
|
|
2364
|
+
ancestor_item=lambda ancestor: InstanceParentBreadcrumbItem(parent_key="location", parent=ancestor),
|
|
2365
|
+
),
|
|
1999
2366
|
InstanceBreadcrumbItem(
|
|
2000
2367
|
instance=lambda c: c["object"].parent_bay.device,
|
|
2001
2368
|
should_render=lambda c: hasattr(c["object"], "parent_bay"),
|
|
@@ -2009,7 +2376,7 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2009
2376
|
|
|
2010
2377
|
def get_queryset(self):
|
|
2011
2378
|
queryset = super().get_queryset()
|
|
2012
|
-
if self.
|
|
2379
|
+
if self.action == "retrieve":
|
|
2013
2380
|
queryset = queryset.select_related(
|
|
2014
2381
|
"controller_managed_device_group__controller",
|
|
2015
2382
|
"device_redundancy_group",
|
|
@@ -2035,34 +2402,6 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2035
2402
|
Override base ObjectDetailContent to render dynamic-groups table as a separate view/tab instead of inline.
|
|
2036
2403
|
"""
|
|
2037
2404
|
|
|
2038
|
-
class DeviceDynamicGroupsTextPanel(object_detail.BaseTextPanel):
|
|
2039
|
-
"""Panel displaying a note about caching of dynamic groups."""
|
|
2040
|
-
|
|
2041
|
-
def __init__(
|
|
2042
|
-
self,
|
|
2043
|
-
*,
|
|
2044
|
-
weight,
|
|
2045
|
-
render_as=object_detail.BaseTextPanel.RenderOptions.MARKDOWN,
|
|
2046
|
-
label="Dynamic Group caching",
|
|
2047
|
-
**kwargs,
|
|
2048
|
-
):
|
|
2049
|
-
super().__init__(weight=weight, render_as=render_as, label=label, **kwargs)
|
|
2050
|
-
|
|
2051
|
-
def get_value(self, context):
|
|
2052
|
-
dg_list_url = reverse("extras:dynamicgroup_list")
|
|
2053
|
-
job_run_url = reverse(
|
|
2054
|
-
"extras:job_run_by_class_path",
|
|
2055
|
-
kwargs={"class_path": "nautobot.core.jobs.groups.RefreshDynamicGroupCaches"},
|
|
2056
|
-
)
|
|
2057
|
-
return (
|
|
2058
|
-
"Dynamic group membership is cached for performance reasons, "
|
|
2059
|
-
"therefore this page may not always be up-to-date.\n\n"
|
|
2060
|
-
"You can refresh the membership of any specific group by viewing it from the list below or from the "
|
|
2061
|
-
f"[Dynamic Groups list view]({dg_list_url}).\n\n"
|
|
2062
|
-
"You can also refresh the membership of **all** groups by running the "
|
|
2063
|
-
f"[Refresh Dynamic Group Caches job]({job_run_url})."
|
|
2064
|
-
)
|
|
2065
|
-
|
|
2066
2405
|
def __init__(self, **kwargs):
|
|
2067
2406
|
super().__init__(**kwargs)
|
|
2068
2407
|
# Remove inline tab definition
|
|
@@ -2078,7 +2417,7 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2078
2417
|
url_name="dcim:device_dynamicgroups",
|
|
2079
2418
|
related_object_attribute="dynamic_groups",
|
|
2080
2419
|
panels=(
|
|
2081
|
-
|
|
2420
|
+
object_detail.DynamicGroupsTextPanel(weight=100),
|
|
2082
2421
|
object_detail.ObjectsTablePanel(
|
|
2083
2422
|
weight=200,
|
|
2084
2423
|
table_class=DynamicGroupTable,
|
|
@@ -2165,11 +2504,11 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2165
2504
|
for powerport in instance.all_power_ports.all():
|
|
2166
2505
|
utilization = powerport.get_power_draw()
|
|
2167
2506
|
# Table row for each power-port
|
|
2168
|
-
|
|
2169
|
-
if
|
|
2170
|
-
available_power =
|
|
2507
|
+
connected_endpoint = powerport.connected_endpoint
|
|
2508
|
+
if isinstance(connected_endpoint, PowerFeed) and connected_endpoint.available_power:
|
|
2509
|
+
available_power = connected_endpoint.available_power
|
|
2171
2510
|
utilization_data = Context(
|
|
2172
|
-
helpers.utilization_graph_raw_data(utilization["allocated"],
|
|
2511
|
+
helpers.utilization_graph_raw_data(utilization["allocated"], connected_endpoint.available_power)
|
|
2173
2512
|
)
|
|
2174
2513
|
utilization_graph = object_detail.render_component_template(
|
|
2175
2514
|
"utilities/templatetags/utilization_graph.html", utilization_data
|
|
@@ -2188,13 +2527,10 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2188
2527
|
|
|
2189
2528
|
# Indented table row for each leg of a three-phase power-port.
|
|
2190
2529
|
for leg in utilization["legs"]:
|
|
2191
|
-
if
|
|
2192
|
-
available_power =
|
|
2530
|
+
if isinstance(connected_endpoint, PowerFeed) and connected_endpoint.available_power:
|
|
2531
|
+
available_power = connected_endpoint.available_power / 3
|
|
2193
2532
|
utilization_data = Context(
|
|
2194
|
-
helpers.utilization_graph_raw_data(leg["allocated"],
|
|
2195
|
-
)
|
|
2196
|
-
utilization_graph = object_detail.render_component_template(
|
|
2197
|
-
"utilities/templatetags/utilization_graph.html", utilization_data
|
|
2533
|
+
helpers.utilization_graph_raw_data(leg["allocated"], connected_endpoint.available_power / 3)
|
|
2198
2534
|
)
|
|
2199
2535
|
else:
|
|
2200
2536
|
available_power = helpers.HTML_NONE
|
|
@@ -2434,6 +2770,7 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2434
2770
|
table_class=VRFDeviceAssignmentTable,
|
|
2435
2771
|
table_filter="device",
|
|
2436
2772
|
exclude_columns=["related_object_type", "related_object_name"],
|
|
2773
|
+
related_list_url_name="ipam:vrf_list",
|
|
2437
2774
|
show_table_config_button=False,
|
|
2438
2775
|
),
|
|
2439
2776
|
object_detail.ObjectsTablePanel(
|
|
@@ -2463,6 +2800,7 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2463
2800
|
table_attribute="images",
|
|
2464
2801
|
related_field_name="device",
|
|
2465
2802
|
show_table_config_button=False,
|
|
2803
|
+
enable_related_link=False,
|
|
2466
2804
|
),
|
|
2467
2805
|
object_detail.ObjectsTablePanel(
|
|
2468
2806
|
weight=100,
|
|
@@ -2773,29 +3111,52 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2773
3111
|
),
|
|
2774
3112
|
),
|
|
2775
3113
|
),
|
|
2776
|
-
|
|
3114
|
+
object_detail.DistinctViewTab(
|
|
2777
3115
|
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB + 1200,
|
|
3116
|
+
tab_id="vpn_endpoints",
|
|
3117
|
+
label="VPN Endpoints",
|
|
3118
|
+
url_name="dcim:device_vpnendpoints",
|
|
3119
|
+
related_object_attribute="vpn_tunnel_endpoints",
|
|
3120
|
+
hide_if_empty=True,
|
|
3121
|
+
panels=(
|
|
3122
|
+
object_detail.ObjectsTablePanel(
|
|
3123
|
+
weight=100,
|
|
3124
|
+
section=SectionChoices.FULL_WIDTH,
|
|
3125
|
+
table_title="VPN Endpoints",
|
|
3126
|
+
table_class=VPNTunnelEndpointTable,
|
|
3127
|
+
table_attribute="vpn_tunnel_endpoints",
|
|
3128
|
+
related_field_name="device",
|
|
3129
|
+
select_related_fields=["source_interface", "role"],
|
|
3130
|
+
exclude_columns=["device"],
|
|
3131
|
+
tab_id="vpn_endpoints",
|
|
3132
|
+
enable_bulk_actions=True,
|
|
3133
|
+
include_paginator=True,
|
|
3134
|
+
),
|
|
3135
|
+
),
|
|
3136
|
+
),
|
|
3137
|
+
DeviceNAPALMTab(
|
|
3138
|
+
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB + 1300,
|
|
2778
3139
|
tab_id="status",
|
|
2779
3140
|
label="Status",
|
|
2780
3141
|
url_name="dcim:device_status",
|
|
2781
3142
|
required_permissions=["dcim.napalm_read_device"],
|
|
2782
3143
|
),
|
|
2783
3144
|
DeviceNAPALMTab(
|
|
2784
|
-
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB +
|
|
3145
|
+
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB + 1400,
|
|
2785
3146
|
tab_id="lldp_neighbors",
|
|
2786
3147
|
label="LLDP Neighbors",
|
|
2787
3148
|
url_name="dcim:device_lldp_neighbors",
|
|
2788
3149
|
required_permissions=["dcim.napalm_read_device"],
|
|
2789
3150
|
),
|
|
2790
3151
|
DeviceNAPALMTab(
|
|
2791
|
-
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB +
|
|
3152
|
+
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB + 1500,
|
|
2792
3153
|
tab_id="config",
|
|
2793
3154
|
label="Configuration",
|
|
2794
3155
|
url_name="dcim:device_config",
|
|
2795
3156
|
required_permissions=["dcim.napalm_read_device"],
|
|
2796
3157
|
),
|
|
2797
3158
|
object_detail.DistinctViewTab(
|
|
2798
|
-
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB +
|
|
3159
|
+
weight=object_detail.Tab.WEIGHT_CHANGELOG_TAB + 1600,
|
|
2799
3160
|
tab_id="config_context",
|
|
2800
3161
|
label="Config Context",
|
|
2801
3162
|
url_name="dcim:device_configcontext",
|
|
@@ -2807,7 +3168,7 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2807
3168
|
def get_extra_context(self, request, instance):
|
|
2808
3169
|
extra_context = super().get_extra_context(request, instance)
|
|
2809
3170
|
|
|
2810
|
-
if self.
|
|
3171
|
+
if self.action == "retrieve":
|
|
2811
3172
|
# VirtualChassis members
|
|
2812
3173
|
if instance.virtual_chassis is not None:
|
|
2813
3174
|
vc_members = (
|
|
@@ -2822,6 +3183,16 @@ class DeviceUIViewSet(NautobotUIViewSet):
|
|
|
2822
3183
|
|
|
2823
3184
|
return extra_context
|
|
2824
3185
|
|
|
3186
|
+
@action(
|
|
3187
|
+
detail=True,
|
|
3188
|
+
url_path="vpn-endpoints",
|
|
3189
|
+
url_name="vpnendpoints",
|
|
3190
|
+
custom_view_base_action="view",
|
|
3191
|
+
custom_view_additional_permissions=["vpn.view_vpntunnelendpoint"],
|
|
3192
|
+
)
|
|
3193
|
+
def vpn_endpoints(self, request, *args, **kwargs):
|
|
3194
|
+
return Response({})
|
|
3195
|
+
|
|
2825
3196
|
@action(
|
|
2826
3197
|
detail=True,
|
|
2827
3198
|
url_path="dynamic-groups",
|
|
@@ -3505,11 +3876,13 @@ class ConsolePortListView(generic.ObjectListView):
|
|
|
3505
3876
|
|
|
3506
3877
|
class ConsolePortView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3507
3878
|
queryset = ConsolePort.objects.all()
|
|
3879
|
+
device_breadcrumb_url = "dcim:device_consoleports"
|
|
3880
|
+
module_breadcrumb_url = "dcim:module_consoleports"
|
|
3508
3881
|
|
|
3509
3882
|
def get_extra_context(self, request, instance):
|
|
3510
3883
|
return {
|
|
3511
|
-
"device_breadcrumb_url":
|
|
3512
|
-
"module_breadcrumb_url":
|
|
3884
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
3885
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3513
3886
|
**super().get_extra_context(request, instance),
|
|
3514
3887
|
}
|
|
3515
3888
|
|
|
@@ -3571,11 +3944,13 @@ class ConsoleServerPortListView(generic.ObjectListView):
|
|
|
3571
3944
|
|
|
3572
3945
|
class ConsoleServerPortView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3573
3946
|
queryset = ConsoleServerPort.objects.all()
|
|
3947
|
+
device_breadcrumb_url = "dcim:device_consoleserverports"
|
|
3948
|
+
module_breadcrumb_url = "dcim:module_consoleserverports"
|
|
3574
3949
|
|
|
3575
3950
|
def get_extra_context(self, request, instance):
|
|
3576
3951
|
return {
|
|
3577
|
-
"device_breadcrumb_url":
|
|
3578
|
-
"module_breadcrumb_url":
|
|
3952
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
3953
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3579
3954
|
**super().get_extra_context(request, instance),
|
|
3580
3955
|
}
|
|
3581
3956
|
|
|
@@ -3637,11 +4012,13 @@ class PowerPortListView(generic.ObjectListView):
|
|
|
3637
4012
|
|
|
3638
4013
|
class PowerPortView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3639
4014
|
queryset = PowerPort.objects.all()
|
|
4015
|
+
device_breadcrumb_url = "dcim:device_powerports"
|
|
4016
|
+
module_breadcrumb_url = "dcim:module_powerports"
|
|
3640
4017
|
|
|
3641
4018
|
def get_extra_context(self, request, instance):
|
|
3642
4019
|
return {
|
|
3643
|
-
"device_breadcrumb_url":
|
|
3644
|
-
"module_breadcrumb_url":
|
|
4020
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4021
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3645
4022
|
**super().get_extra_context(request, instance),
|
|
3646
4023
|
}
|
|
3647
4024
|
|
|
@@ -3703,11 +4080,13 @@ class PowerOutletListView(generic.ObjectListView):
|
|
|
3703
4080
|
|
|
3704
4081
|
class PowerOutletView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3705
4082
|
queryset = PowerOutlet.objects.all()
|
|
4083
|
+
device_breadcrumb_url = "dcim:device_poweroutlets"
|
|
4084
|
+
module_breadcrumb_url = "dcim:module_poweroutlets"
|
|
3706
4085
|
|
|
3707
4086
|
def get_extra_context(self, request, instance):
|
|
3708
4087
|
return {
|
|
3709
|
-
"device_breadcrumb_url":
|
|
3710
|
-
"module_breadcrumb_url":
|
|
4088
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4089
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3711
4090
|
**super().get_extra_context(request, instance),
|
|
3712
4091
|
}
|
|
3713
4092
|
|
|
@@ -3772,6 +4151,8 @@ class InterfaceView(
|
|
|
3772
4151
|
generic.ObjectView,
|
|
3773
4152
|
):
|
|
3774
4153
|
queryset = Interface.objects.all()
|
|
4154
|
+
device_breadcrumb_url = "dcim:device_interfaces"
|
|
4155
|
+
module_breadcrumb_url = "dcim:module_interfaces"
|
|
3775
4156
|
|
|
3776
4157
|
def get_extra_context(self, request, instance):
|
|
3777
4158
|
# Get assigned IP addresses
|
|
@@ -3812,8 +4193,8 @@ class InterfaceView(
|
|
|
3812
4193
|
return {
|
|
3813
4194
|
"ipaddress_table": ipaddress_table,
|
|
3814
4195
|
"vlan_table": vlan_table,
|
|
3815
|
-
"device_breadcrumb_url":
|
|
3816
|
-
"module_breadcrumb_url":
|
|
4196
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4197
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3817
4198
|
"child_interfaces_table": child_interfaces_tables,
|
|
3818
4199
|
"redundancy_table": redundancy_table,
|
|
3819
4200
|
"virtual_device_contexts_table": virtual_device_contexts_table,
|
|
@@ -3902,11 +4283,13 @@ class FrontPortListView(generic.ObjectListView):
|
|
|
3902
4283
|
|
|
3903
4284
|
class FrontPortView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3904
4285
|
queryset = FrontPort.objects.all()
|
|
4286
|
+
device_breadcrumb_url = "dcim:device_frontports"
|
|
4287
|
+
module_breadcrumb_url = "dcim:module_frontports"
|
|
3905
4288
|
|
|
3906
4289
|
def get_extra_context(self, request, instance):
|
|
3907
4290
|
return {
|
|
3908
|
-
"device_breadcrumb_url":
|
|
3909
|
-
"module_breadcrumb_url":
|
|
4291
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4292
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3910
4293
|
**super().get_extra_context(request, instance),
|
|
3911
4294
|
}
|
|
3912
4295
|
|
|
@@ -3968,11 +4351,13 @@ class RearPortListView(generic.ObjectListView):
|
|
|
3968
4351
|
|
|
3969
4352
|
class RearPortView(DeviceComponentPageMixin, generic.ObjectView):
|
|
3970
4353
|
queryset = RearPort.objects.all()
|
|
4354
|
+
device_breadcrumb_url = "dcim:device_rearports"
|
|
4355
|
+
module_breadcrumb_url = "dcim:module_rearports"
|
|
3971
4356
|
|
|
3972
4357
|
def get_extra_context(self, request, instance):
|
|
3973
4358
|
return {
|
|
3974
|
-
"device_breadcrumb_url":
|
|
3975
|
-
"module_breadcrumb_url":
|
|
4359
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4360
|
+
"module_breadcrumb_url": self.module_breadcrumb_url,
|
|
3976
4361
|
**super().get_extra_context(request, instance),
|
|
3977
4362
|
}
|
|
3978
4363
|
|
|
@@ -4034,9 +4419,13 @@ class DeviceBayListView(generic.ObjectListView):
|
|
|
4034
4419
|
|
|
4035
4420
|
class DeviceBayView(DeviceComponentPageMixin, generic.ObjectView):
|
|
4036
4421
|
queryset = DeviceBay.objects.all()
|
|
4422
|
+
device_breadcrumb_url = "dcim:device_devicebays"
|
|
4037
4423
|
|
|
4038
4424
|
def get_extra_context(self, request, instance):
|
|
4039
|
-
return {
|
|
4425
|
+
return {
|
|
4426
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4427
|
+
**super().get_extra_context(request, instance),
|
|
4428
|
+
}
|
|
4040
4429
|
|
|
4041
4430
|
|
|
4042
4431
|
class DeviceBayCreateView(generic.ComponentCreateView):
|
|
@@ -4271,6 +4660,7 @@ class InventoryItemListView(generic.ObjectListView):
|
|
|
4271
4660
|
|
|
4272
4661
|
class InventoryItemView(DeviceComponentPageMixin, generic.ObjectView):
|
|
4273
4662
|
queryset = InventoryItem.objects.all().select_related("device", "manufacturer", "software_version")
|
|
4663
|
+
device_breadcrumb_url = "dcim:device_inventory"
|
|
4274
4664
|
|
|
4275
4665
|
def get_extra_context(self, request, instance):
|
|
4276
4666
|
# Software images
|
|
@@ -4280,7 +4670,7 @@ class InventoryItemView(DeviceComponentPageMixin, generic.ObjectView):
|
|
|
4280
4670
|
software_version_images = []
|
|
4281
4671
|
|
|
4282
4672
|
return {
|
|
4283
|
-
"device_breadcrumb_url":
|
|
4673
|
+
"device_breadcrumb_url": self.device_breadcrumb_url,
|
|
4284
4674
|
"software_version_images": software_version_images,
|
|
4285
4675
|
**super().get_extra_context(request, instance),
|
|
4286
4676
|
}
|
|
@@ -4444,18 +4834,23 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
|
|
|
4444
4834
|
#
|
|
4445
4835
|
# Cables
|
|
4446
4836
|
#
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4837
|
+
class CableUIViewSet(NautobotUIViewSet):
|
|
4838
|
+
bulk_update_form_class = forms.CableBulkEditForm
|
|
4839
|
+
filterset_class = filters.CableFilterSet
|
|
4840
|
+
filterset_form_class = forms.CableFilterForm
|
|
4841
|
+
form_class = forms.CableForm
|
|
4842
|
+
serializer_class = serializers.CableSerializer
|
|
4843
|
+
table_class = tables.CableTable
|
|
4844
|
+
queryset = Cable.objects.prefetch_related("termination_a", "termination_b")
|
|
4454
4845
|
action_buttons = ("import", "export")
|
|
4455
4846
|
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4847
|
+
def get_queryset(self):
|
|
4848
|
+
# 6933 fix: with prefetch related in queryset
|
|
4849
|
+
# DeviceInterface is not properly cleared of _path_id
|
|
4850
|
+
queryset = super().get_queryset()
|
|
4851
|
+
if self.action == "destroy":
|
|
4852
|
+
queryset = queryset.prefetch_related(None)
|
|
4853
|
+
return queryset
|
|
4459
4854
|
|
|
4460
4855
|
|
|
4461
4856
|
class PathTraceView(generic.ObjectView):
|
|
@@ -4465,6 +4860,7 @@ class PathTraceView(generic.ObjectView):
|
|
|
4465
4860
|
|
|
4466
4861
|
additional_permissions = ["dcim.view_cable"]
|
|
4467
4862
|
template_name = "dcim/cable_trace.html"
|
|
4863
|
+
view_titles = Titles(titles={"detail": "Cable Trace for {{ object }}"})
|
|
4468
4864
|
|
|
4469
4865
|
def dispatch(self, request, *args, **kwargs):
|
|
4470
4866
|
model = kwargs.pop("model")
|
|
@@ -4501,6 +4897,7 @@ class PathTraceView(generic.ObjectView):
|
|
|
4501
4897
|
"path": path,
|
|
4502
4898
|
"related_paths": related_paths,
|
|
4503
4899
|
"total_length": path.get_total_length() if path else None,
|
|
4900
|
+
"view_titles": self.get_view_titles(),
|
|
4504
4901
|
**super().get_extra_context(request, instance),
|
|
4505
4902
|
}
|
|
4506
4903
|
|
|
@@ -4585,34 +4982,6 @@ class CableCreateView(generic.ObjectEditView):
|
|
|
4585
4982
|
)
|
|
4586
4983
|
|
|
4587
4984
|
|
|
4588
|
-
class CableEditView(generic.ObjectEditView):
|
|
4589
|
-
queryset = Cable.objects.all()
|
|
4590
|
-
model_form = forms.CableForm
|
|
4591
|
-
template_name = "dcim/cable_edit.html"
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
class CableDeleteView(generic.ObjectDeleteView):
|
|
4595
|
-
queryset = Cable.objects.all()
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
class CableBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
|
|
4599
|
-
queryset = Cable.objects.all()
|
|
4600
|
-
table = tables.CableTable
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
class CableBulkEditView(generic.BulkEditView):
|
|
4604
|
-
queryset = Cable.objects.prefetch_related("termination_a", "termination_b")
|
|
4605
|
-
filterset = filters.CableFilterSet
|
|
4606
|
-
table = tables.CableTable
|
|
4607
|
-
form = forms.CableBulkEditForm
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
class CableBulkDeleteView(generic.BulkDeleteView):
|
|
4611
|
-
queryset = Cable.objects.prefetch_related("termination_a", "termination_b")
|
|
4612
|
-
filterset = filters.CableFilterSet
|
|
4613
|
-
table = tables.CableTable
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
4985
|
#
|
|
4617
4986
|
# Connections
|
|
4618
4987
|
#
|
|
@@ -4815,7 +5184,6 @@ class VirtualChassisUIViewSet(NautobotUIViewSet):
|
|
|
4815
5184
|
url_path="add-member",
|
|
4816
5185
|
url_name="add_member",
|
|
4817
5186
|
custom_view_base_action="change",
|
|
4818
|
-
custom_view_additional_permissions=["dcim.change_virtualchassis"],
|
|
4819
5187
|
)
|
|
4820
5188
|
def add_member(self, request, pk=None):
|
|
4821
5189
|
virtual_chassis = self.get_object()
|
|
@@ -5012,7 +5380,9 @@ class PowerFeedUIViewSet(NautobotUIViewSet):
|
|
|
5012
5380
|
items={
|
|
5013
5381
|
"detail": [
|
|
5014
5382
|
ModelBreadcrumbItem(),
|
|
5015
|
-
|
|
5383
|
+
AncestorsInstanceBreadcrumbItem(
|
|
5384
|
+
instance=context_object_attr("power_panel.location"), include_self=True
|
|
5385
|
+
),
|
|
5016
5386
|
InstanceBreadcrumbItem(instance=context_object_attr("power_panel")),
|
|
5017
5387
|
InstanceBreadcrumbItem(
|
|
5018
5388
|
instance=context_object_attr("rack"),
|
|
@@ -5164,7 +5534,7 @@ class PowerFeedUIViewSet(NautobotUIViewSet):
|
|
|
5164
5534
|
+ f"?return_url={instance.get_absolute_url()}"
|
|
5165
5535
|
)
|
|
5166
5536
|
connect_link = format_html(
|
|
5167
|
-
'<a href="{}" class="btn btn-primary btn-sm
|
|
5537
|
+
'<a href="{}" class="btn btn-primary btn-sm float-end">'
|
|
5168
5538
|
'<span class="mdi mdi-ethernet-cable" aria-hidden="true"></span> Connect</a>',
|
|
5169
5539
|
connect_url,
|
|
5170
5540
|
)
|
|
@@ -5248,7 +5618,8 @@ class InterfaceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
5248
5618
|
prefetch_related_fields=["interface"],
|
|
5249
5619
|
order_by_fields=["priority"],
|
|
5250
5620
|
table_title="Interfaces",
|
|
5251
|
-
related_field_name="
|
|
5621
|
+
related_field_name="interface_redundancy_groups",
|
|
5622
|
+
related_list_url_name="dcim:interface_list",
|
|
5252
5623
|
include_columns=[
|
|
5253
5624
|
"interface__device",
|
|
5254
5625
|
"interface",
|
|
@@ -5501,6 +5872,14 @@ class SoftwareVersionUIViewSet(NautobotUIViewSet):
|
|
|
5501
5872
|
queryset = SoftwareVersion.objects.all()
|
|
5502
5873
|
serializer_class = serializers.SoftwareVersionSerializer
|
|
5503
5874
|
table_class = tables.SoftwareVersionTable
|
|
5875
|
+
breadcrumbs = Breadcrumbs(
|
|
5876
|
+
items={
|
|
5877
|
+
"detail": [
|
|
5878
|
+
ModelBreadcrumbItem(),
|
|
5879
|
+
InstanceBreadcrumbItem(instance=context_object_attr("platform")),
|
|
5880
|
+
]
|
|
5881
|
+
}
|
|
5882
|
+
)
|
|
5504
5883
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
5505
5884
|
panels=(
|
|
5506
5885
|
object_detail.ObjectFieldsPanel(
|
|
@@ -5578,7 +5957,7 @@ class ControllerUIViewSet(NautobotUIViewSet):
|
|
|
5578
5957
|
object_detail.DistinctViewTab(
|
|
5579
5958
|
weight=700,
|
|
5580
5959
|
tab_id="wireless_networks",
|
|
5581
|
-
url_name="dcim:
|
|
5960
|
+
url_name="dcim:controller_wireless_networks",
|
|
5582
5961
|
label="Wireless Networks",
|
|
5583
5962
|
related_object_attribute="wireless_network_assignments",
|
|
5584
5963
|
panels=(
|
|
@@ -5593,6 +5972,7 @@ class ControllerUIViewSet(NautobotUIViewSet):
|
|
|
5593
5972
|
select_related_fields=["wireless_network"],
|
|
5594
5973
|
exclude_columns=["controller"],
|
|
5595
5974
|
include_paginator=True,
|
|
5975
|
+
enable_related_link=False,
|
|
5596
5976
|
),
|
|
5597
5977
|
),
|
|
5598
5978
|
),
|
|
@@ -5602,12 +5982,12 @@ class ControllerUIViewSet(NautobotUIViewSet):
|
|
|
5602
5982
|
@action(
|
|
5603
5983
|
detail=True,
|
|
5604
5984
|
url_path="wireless-networks",
|
|
5605
|
-
url_name="
|
|
5985
|
+
url_name="wireless_networks",
|
|
5606
5986
|
methods=["get"],
|
|
5607
5987
|
custom_view_base_action="view",
|
|
5608
5988
|
custom_view_additional_permissions=["wireless.view_controllermanageddevicegroupwirelessnetworkassignment"],
|
|
5609
5989
|
)
|
|
5610
|
-
def
|
|
5990
|
+
def wireless_networks(self, request, *args, **kwargs):
|
|
5611
5991
|
return Response({})
|
|
5612
5992
|
|
|
5613
5993
|
|