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
|
@@ -4,7 +4,6 @@ import contextlib
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from enum import Enum
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Any
|
|
8
7
|
import uuid
|
|
9
8
|
|
|
10
9
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -43,10 +42,11 @@ from nautobot.core.templatetags.helpers import (
|
|
|
43
42
|
from nautobot.core.ui.choices import LayoutChoices, SectionChoices
|
|
44
43
|
from nautobot.core.ui.echarts import EChartsBase
|
|
45
44
|
from nautobot.core.ui.utils import render_component_template
|
|
46
|
-
from nautobot.core.utils.lookup import get_filterset_for_model, get_route_for_model
|
|
45
|
+
from nautobot.core.utils.lookup import get_filterset_for_model, get_route_for_model, get_view_for_model
|
|
47
46
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
48
47
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
49
48
|
from nautobot.core.views.utils import get_obj_from_context
|
|
49
|
+
from nautobot.data_validation.tables import DataComplianceTable
|
|
50
50
|
from nautobot.dcim.models import Rack
|
|
51
51
|
from nautobot.extras.choices import CustomFieldTypeChoices
|
|
52
52
|
from nautobot.extras.tables import AssociatedContactsTable, DynamicGroupTable, ObjectMetadataTable
|
|
@@ -101,6 +101,7 @@ class ObjectDetailContent:
|
|
|
101
101
|
_ObjectDetailContactsTab(),
|
|
102
102
|
_ObjectDetailGroupsTab(),
|
|
103
103
|
_ObjectDetailMetadataTab(),
|
|
104
|
+
_ObjectDetailDataComplianceTab(),
|
|
104
105
|
]
|
|
105
106
|
if extra_tabs is not None:
|
|
106
107
|
tabs.extend(extra_tabs)
|
|
@@ -245,6 +246,9 @@ class Button(Component):
|
|
|
245
246
|
"""
|
|
246
247
|
if self.link_name and self.link_includes_pk:
|
|
247
248
|
obj = get_obj_from_context(context, self.context_object_key)
|
|
249
|
+
if not obj:
|
|
250
|
+
logger.warning("Button %s has no object to link to", self.label)
|
|
251
|
+
return None
|
|
248
252
|
return reverse(self.link_name, kwargs={"pk": obj.pk})
|
|
249
253
|
elif self.link_name:
|
|
250
254
|
return reverse(self.link_name)
|
|
@@ -262,8 +266,11 @@ class Button(Component):
|
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
def should_render(self, context: Context):
|
|
269
|
+
# Only show if the user has the permission, which is enforce in super.
|
|
265
270
|
if not super().should_render(context):
|
|
266
271
|
return False
|
|
272
|
+
if self.render_on_tab_id == "__all__":
|
|
273
|
+
return True
|
|
267
274
|
return context.get("active_tab", "main") == self.render_on_tab_id
|
|
268
275
|
|
|
269
276
|
def render(self, context: Context):
|
|
@@ -305,6 +312,7 @@ class FormButton(Button):
|
|
|
305
312
|
self,
|
|
306
313
|
form_id: str,
|
|
307
314
|
link_name: str,
|
|
315
|
+
render_on_tab_id="__all__",
|
|
308
316
|
template_path="components/button/formbutton.html",
|
|
309
317
|
**kwargs,
|
|
310
318
|
):
|
|
@@ -326,7 +334,7 @@ class FormButton(Button):
|
|
|
326
334
|
if not self.form_id:
|
|
327
335
|
raise ValueError("FormButton requires 'form_id' to be set in ObjectsTablePanel.")
|
|
328
336
|
|
|
329
|
-
super().__init__(link_name=link_name, template_path=template_path, **kwargs)
|
|
337
|
+
super().__init__(link_name=link_name, render_on_tab_id=render_on_tab_id, template_path=template_path, **kwargs)
|
|
330
338
|
|
|
331
339
|
def get_extra_context(self, context: Context):
|
|
332
340
|
return {
|
|
@@ -377,8 +385,9 @@ class Tab(Component):
|
|
|
377
385
|
WEIGHT_CONTACTS_TAB = 300
|
|
378
386
|
WEIGHT_GROUPS_TAB = 400
|
|
379
387
|
WEIGHT_METADATA_TAB = 500
|
|
380
|
-
|
|
381
|
-
|
|
388
|
+
WEIGHT_DATACOMPLIANCE_TAB = 600
|
|
389
|
+
WEIGHT_NOTES_TAB = 700 # reserved, not yet using this framework
|
|
390
|
+
WEIGHT_CHANGELOG_TAB = 800 # reserved, not yet using this framework
|
|
382
391
|
|
|
383
392
|
def panels_for_section(self, section):
|
|
384
393
|
"""
|
|
@@ -540,6 +549,7 @@ class Panel(Component):
|
|
|
540
549
|
self,
|
|
541
550
|
*,
|
|
542
551
|
label="",
|
|
552
|
+
css_class="default",
|
|
543
553
|
section=SectionChoices.FULL_WIDTH,
|
|
544
554
|
body_id=None,
|
|
545
555
|
body_content_template_path=None,
|
|
@@ -554,6 +564,7 @@ class Panel(Component):
|
|
|
554
564
|
|
|
555
565
|
Args:
|
|
556
566
|
label (str): Label to display for this panel. Optional; if an empty string, the panel will have no label.
|
|
567
|
+
css_class (str): Panel variant to render as, e.g. "default", "warning", "info".
|
|
557
568
|
section (str): One of the [`SectionChoices`](./ui.md#nautobot.apps.ui.SectionChoices) values, indicating the layout section this Panel belongs to.
|
|
558
569
|
body_id (str): HTML element `id` to attach to the rendered body wrapper of the panel.
|
|
559
570
|
body_content_template_path (str): Template path to render the content contained *within* the panel body.
|
|
@@ -565,6 +576,7 @@ class Panel(Component):
|
|
|
565
576
|
(a `div` or `table`) as well as its contents. Generally you won't override this as a user.
|
|
566
577
|
"""
|
|
567
578
|
self.label = label
|
|
579
|
+
self.css_class = css_class
|
|
568
580
|
self.section = section
|
|
569
581
|
self.body_id = body_id
|
|
570
582
|
self.body_content_template_path = body_content_template_path
|
|
@@ -593,6 +605,7 @@ class Panel(Component):
|
|
|
593
605
|
self.template_path,
|
|
594
606
|
context,
|
|
595
607
|
label=self.render_label(context),
|
|
608
|
+
css_class=self.css_class,
|
|
596
609
|
header_extra_content=self.render_header_extra_content(context),
|
|
597
610
|
body=self.render_body(context),
|
|
598
611
|
footer_content=self.render_footer_content(context),
|
|
@@ -758,6 +771,7 @@ class ObjectsTablePanel(Panel):
|
|
|
758
771
|
select_related_fields=None,
|
|
759
772
|
prefetch_related_fields=None,
|
|
760
773
|
order_by_fields=None,
|
|
774
|
+
# TODO: Is `table_title` redundant with the base Panel's `label`?
|
|
761
775
|
table_title=None,
|
|
762
776
|
max_display_count=None,
|
|
763
777
|
paginate=True,
|
|
@@ -768,6 +782,8 @@ class ObjectsTablePanel(Panel):
|
|
|
768
782
|
add_permissions=None,
|
|
769
783
|
hide_hierarchy_ui=False,
|
|
770
784
|
related_field_name=None,
|
|
785
|
+
related_list_url_name=None,
|
|
786
|
+
enable_related_link=True,
|
|
771
787
|
enable_bulk_actions=False,
|
|
772
788
|
tab_id=None,
|
|
773
789
|
body_wrapper_template_path="components/panel/body_wrapper_table.html",
|
|
@@ -819,6 +835,11 @@ class ObjectsTablePanel(Panel):
|
|
|
819
835
|
hide_hierarchy_ui (bool, optional): Don't display hierarchy-based indentation of tree models in this table
|
|
820
836
|
related_field_name (str, optional): The name of the filter/form field for the related model that links back
|
|
821
837
|
to the base model. Defaults to the same as `table_filter` if unset. Used to populate URLs.
|
|
838
|
+
related_list_url_name (str, optional): The URL used to generate the list button URL for the related model.
|
|
839
|
+
If not provided, the default table's model `list` route is used.
|
|
840
|
+
This can be useful when the related model is a many-to-many relationship with a custom through table.
|
|
841
|
+
enable_related_link (bool, optional): If True, the badge on the related model will be a link to the related model list view.
|
|
842
|
+
When False, the badge will still show the count of the related model, but will not be a link.
|
|
822
843
|
enable_bulk_actions (bool, optional): Show the pk toggle columns on the table if the user has the
|
|
823
844
|
appropriate permissions.
|
|
824
845
|
tab_id (str, optional): The ID of the tab this panel belongs to. Used to append to a `return_url` when
|
|
@@ -869,6 +890,8 @@ class ObjectsTablePanel(Panel):
|
|
|
869
890
|
self.add_permissions = add_permissions or []
|
|
870
891
|
self.hide_hierarchy_ui = hide_hierarchy_ui
|
|
871
892
|
self.related_field_name = related_field_name
|
|
893
|
+
self.related_list_url_name = related_list_url_name
|
|
894
|
+
self.enable_related_link = enable_related_link
|
|
872
895
|
self.enable_bulk_actions = enable_bulk_actions
|
|
873
896
|
self.tab_id = tab_id
|
|
874
897
|
self.footer_buttons = footer_buttons
|
|
@@ -895,7 +918,12 @@ class ObjectsTablePanel(Panel):
|
|
|
895
918
|
related_field_name = self.related_field_name or self.table_filter or obj._meta.model_name
|
|
896
919
|
return_url = context.get("return_url", obj.get_absolute_url())
|
|
897
920
|
if self.tab_id:
|
|
898
|
-
|
|
921
|
+
try:
|
|
922
|
+
# Check to see if the this is a NautobotUIViewset action
|
|
923
|
+
view = get_view_for_model(obj._meta.model)
|
|
924
|
+
return_url += getattr(view, self.tab_id).url_path + "/"
|
|
925
|
+
except AttributeError:
|
|
926
|
+
return_url += f"?tab={self.tab_id}"
|
|
899
927
|
|
|
900
928
|
if self.add_button_route is not None:
|
|
901
929
|
add_permissions = self.add_permissions
|
|
@@ -1024,29 +1052,35 @@ class ObjectsTablePanel(Panel):
|
|
|
1024
1052
|
body_content_table_model = body_content_table.Meta.model
|
|
1025
1053
|
related_field_name = self.related_field_name or self.table_filter or obj._meta.model_name
|
|
1026
1054
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1055
|
+
body_content_table_list_url = None
|
|
1056
|
+
body_content_table_add_url = self._get_table_add_url(context)
|
|
1057
|
+
table_title = self.table_title or body_content_table_model._meta.verbose_name_plural
|
|
1030
1058
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1059
|
+
if self.enable_related_link:
|
|
1060
|
+
list_url = self.related_list_url_name or getattr(self.table_class, "list_url", None)
|
|
1061
|
+
if not list_url:
|
|
1062
|
+
list_url = get_route_for_model(body_content_table_model, "list")
|
|
1035
1063
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1064
|
+
try:
|
|
1065
|
+
list_route = reverse(list_url)
|
|
1066
|
+
except NoReverseMatch:
|
|
1067
|
+
logger.warning(
|
|
1068
|
+
f"Unable to determine a valid list URL for ObjectsTablePanel `{table_title}`"
|
|
1069
|
+
f" related to `{body_content_table_model.__name__}` with `{list_url}`."
|
|
1070
|
+
" If the related object is using a through table, consider setting the `related_list_url_name`"
|
|
1071
|
+
" parameter or disabling the related link via 'enable_related_link=False'."
|
|
1072
|
+
)
|
|
1073
|
+
list_route = None
|
|
1040
1074
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1075
|
+
if list_route:
|
|
1076
|
+
body_content_table_list_url = f"{list_route}?{related_field_name}={obj.pk}"
|
|
1043
1077
|
|
|
1044
1078
|
return {
|
|
1045
1079
|
"body_content_table": body_content_table,
|
|
1046
1080
|
"body_content_table_add_url": body_content_table_add_url,
|
|
1047
1081
|
"body_content_table_list_url": body_content_table_list_url,
|
|
1048
1082
|
"body_content_table_verbose_name": body_content_table_model._meta.verbose_name,
|
|
1049
|
-
"body_content_table_verbose_name_plural":
|
|
1083
|
+
"body_content_table_verbose_name_plural": table_title,
|
|
1050
1084
|
"footer_buttons": self.footer_buttons,
|
|
1051
1085
|
"form_id": self.form_id,
|
|
1052
1086
|
"more_queryset_count": more_queryset_count,
|
|
@@ -1299,28 +1333,13 @@ class EChartsPanel(Panel, EChartsBase):
|
|
|
1299
1333
|
self.width = width
|
|
1300
1334
|
self.height = height
|
|
1301
1335
|
self.chart_container_id = chart_container_id
|
|
1336
|
+
self.body_id = (
|
|
1337
|
+
self.chart_container_id or f"{slugify('echart-' + chart_kwargs.get('header', ''))}-{uuid.uuid4().hex[:8]}"
|
|
1338
|
+
)
|
|
1302
1339
|
|
|
1303
|
-
super().__init__(body_wrapper_template_path=body_wrapper_template_path, **kwargs)
|
|
1340
|
+
super().__init__(body_wrapper_template_path=body_wrapper_template_path, body_id=self.body_id, **kwargs)
|
|
1304
1341
|
EChartsBase.__init__(self, **chart_kwargs)
|
|
1305
1342
|
|
|
1306
|
-
def get_data(self, context: Context) -> dict[str, Any] | None:
|
|
1307
|
-
"""Get the data for chart.
|
|
1308
|
-
|
|
1309
|
-
Args:
|
|
1310
|
-
context (Context): The template or request context.
|
|
1311
|
-
|
|
1312
|
-
Returns:
|
|
1313
|
-
dict[str, Any] | None:
|
|
1314
|
-
- A dictionary in internal chart format, e.g.:
|
|
1315
|
-
{"x": [...], "series": [{"name": str, "data": [...]}]}
|
|
1316
|
-
- A nested dictionary of series, e.g.:
|
|
1317
|
-
{"Series1": {"x1": val1, "x2": val2}, ...}
|
|
1318
|
-
- `None` if no data is set.
|
|
1319
|
-
"""
|
|
1320
|
-
if callable(self.data):
|
|
1321
|
-
return self.data(context) # pylint: disable=not-callable
|
|
1322
|
-
return self.data
|
|
1323
|
-
|
|
1324
1343
|
def should_render(self, context: Context):
|
|
1325
1344
|
"""Determine if the panel should be rendered."""
|
|
1326
1345
|
if not super().should_render(context):
|
|
@@ -1336,16 +1355,14 @@ class EChartsPanel(Panel, EChartsBase):
|
|
|
1336
1355
|
|
|
1337
1356
|
def get_extra_context(self, context: Context):
|
|
1338
1357
|
"""Add chart-specific context variables."""
|
|
1339
|
-
|
|
1340
|
-
chart_config = self.get_config()
|
|
1358
|
+
chart_config = self.get_config(context=context)
|
|
1341
1359
|
return {
|
|
1342
1360
|
**super().get_extra_context(context),
|
|
1343
1361
|
"chart": self,
|
|
1344
1362
|
"chart_config": chart_config,
|
|
1345
1363
|
"chart_width": self.width,
|
|
1346
1364
|
"chart_height": self.height,
|
|
1347
|
-
"chart_container_id": self.
|
|
1348
|
-
or f"{slugify(f'echart-{self.header}')}-{uuid.uuid4().hex[:8]}",
|
|
1365
|
+
"chart_container_id": self.body_id,
|
|
1349
1366
|
}
|
|
1350
1367
|
|
|
1351
1368
|
|
|
@@ -1542,7 +1559,7 @@ class GroupedKeyValueTablePanel(KeyValueTablePanel):
|
|
|
1542
1559
|
super().__init__(body_id=body_id, **kwargs)
|
|
1543
1560
|
|
|
1544
1561
|
def render_header_extra_content(self, context: Context):
|
|
1545
|
-
"""Add a "Collapse All" button to the header."""
|
|
1562
|
+
"""Add a "Collapse All Groups" button to the header."""
|
|
1546
1563
|
return format_html(
|
|
1547
1564
|
"""
|
|
1548
1565
|
<button
|
|
@@ -1552,7 +1569,7 @@ class GroupedKeyValueTablePanel(KeyValueTablePanel):
|
|
|
1552
1569
|
data-nb-toggle="collapse-all"
|
|
1553
1570
|
type="button"
|
|
1554
1571
|
>
|
|
1555
|
-
Collapse All
|
|
1572
|
+
Collapse All Groups
|
|
1556
1573
|
</button>
|
|
1557
1574
|
""",
|
|
1558
1575
|
body_id=self.body_id,
|
|
@@ -1727,7 +1744,7 @@ class StatsPanel(Panel):
|
|
|
1727
1744
|
instance = get_obj_from_context(context)
|
|
1728
1745
|
request = context["request"]
|
|
1729
1746
|
if isinstance(instance, TreeModel):
|
|
1730
|
-
self.filter_pks = (
|
|
1747
|
+
self.filter_pks = list(
|
|
1731
1748
|
instance.descendants(include_self=True).restrict(request.user, "view").values_list("pk", flat=True)
|
|
1732
1749
|
)
|
|
1733
1750
|
else:
|
|
@@ -1743,16 +1760,17 @@ class StatsPanel(Panel):
|
|
|
1743
1760
|
else:
|
|
1744
1761
|
related_object_model_class, query = related_field, f"{self.filter_name}__in"
|
|
1745
1762
|
filter_dict = {query: self.filter_pks}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1763
|
+
qs = related_object_model_class.objects.restrict(request.user, "view").filter(**filter_dict)
|
|
1764
|
+
if len(self.filter_pks) > 1:
|
|
1765
|
+
qs = qs.distinct()
|
|
1766
|
+
related_object_count = qs.count()
|
|
1749
1767
|
related_object_model_class_meta = related_object_model_class._meta
|
|
1750
1768
|
related_object_list_url = validated_viewname(related_object_model_class, "list")
|
|
1751
1769
|
related_object_title = bettertitle(related_object_model_class_meta.verbose_name_plural)
|
|
1752
1770
|
value = [related_object_list_url, related_object_count, related_object_title]
|
|
1753
1771
|
stats[related_object_model_class] = value
|
|
1754
1772
|
related_object_model_filterset = get_filterset_for_model(related_object_model_class)
|
|
1755
|
-
if self.filter_name not in related_object_model_filterset.
|
|
1773
|
+
if self.filter_name not in related_object_model_filterset.base_filters:
|
|
1756
1774
|
raise FieldDoesNotExist(
|
|
1757
1775
|
f"{self.filter_name} is not a valid filter field for {related_object_model_class_meta.verbose_name}"
|
|
1758
1776
|
)
|
|
@@ -2131,8 +2149,8 @@ class _ObjectDetailContactsTab(Tab):
|
|
|
2131
2149
|
max_display_count=100, # since there isn't a separate list view for ContactAssociations!
|
|
2132
2150
|
# TODO: we should provide a standard reusable component template for bulk-actions in the footer
|
|
2133
2151
|
footer_content_template_path="components/panel/footer_contacts_table.html",
|
|
2134
|
-
|
|
2135
|
-
|
|
2152
|
+
enable_related_link=False,
|
|
2153
|
+
table_title="Contacts/Teams",
|
|
2136
2154
|
),
|
|
2137
2155
|
)
|
|
2138
2156
|
super().__init__(tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
|
|
@@ -2153,6 +2171,76 @@ class _ObjectDetailContactsTab(Tab):
|
|
|
2153
2171
|
)
|
|
2154
2172
|
|
|
2155
2173
|
|
|
2174
|
+
class _ObjectDetailDataComplianceTab(DistinctViewTab):
|
|
2175
|
+
"""Built-in class for a Tab displaying information about data compliance."""
|
|
2176
|
+
|
|
2177
|
+
def __init__(
|
|
2178
|
+
self,
|
|
2179
|
+
*,
|
|
2180
|
+
tab_id="data_compliance",
|
|
2181
|
+
label="Data Compliance",
|
|
2182
|
+
weight=Tab.WEIGHT_DATACOMPLIANCE_TAB,
|
|
2183
|
+
panels=None,
|
|
2184
|
+
**kwargs,
|
|
2185
|
+
):
|
|
2186
|
+
if panels is None:
|
|
2187
|
+
panels = (
|
|
2188
|
+
ObjectsTablePanel(
|
|
2189
|
+
weight=100,
|
|
2190
|
+
table_class=DataComplianceTable,
|
|
2191
|
+
table_attribute="associated_data_compliance",
|
|
2192
|
+
related_field_name="object_id",
|
|
2193
|
+
table_title="Data Compliance",
|
|
2194
|
+
add_button_route=None,
|
|
2195
|
+
include_paginator=True,
|
|
2196
|
+
),
|
|
2197
|
+
)
|
|
2198
|
+
super().__init__(url_name="", tab_id=tab_id, label=label, weight=weight, panels=panels, **kwargs)
|
|
2199
|
+
|
|
2200
|
+
def get_extra_context(self, context: Context):
|
|
2201
|
+
return {"url": get_obj_from_context(context).get_data_compliance_url()}
|
|
2202
|
+
|
|
2203
|
+
def should_render(self, context: Context):
|
|
2204
|
+
if not super().should_render(context):
|
|
2205
|
+
return False
|
|
2206
|
+
obj = get_obj_from_context(context)
|
|
2207
|
+
if getattr(obj, "is_data_compliance_model", False):
|
|
2208
|
+
if obj.get_data_compliance_url() is not None:
|
|
2209
|
+
return True
|
|
2210
|
+
logger.warning("Missing data-compliance URL for %r", obj)
|
|
2211
|
+
return False
|
|
2212
|
+
|
|
2213
|
+
|
|
2214
|
+
class DynamicGroupsTextPanel(BaseTextPanel):
|
|
2215
|
+
"""Panel displaying a note about caching of dynamic groups."""
|
|
2216
|
+
|
|
2217
|
+
def __init__(
|
|
2218
|
+
self,
|
|
2219
|
+
*,
|
|
2220
|
+
weight,
|
|
2221
|
+
render_as=BaseTextPanel.RenderOptions.MARKDOWN,
|
|
2222
|
+
label="Dynamic Group caching",
|
|
2223
|
+
css_class="warning",
|
|
2224
|
+
**kwargs,
|
|
2225
|
+
):
|
|
2226
|
+
super().__init__(weight=weight, render_as=render_as, label=label, css_class=css_class, **kwargs)
|
|
2227
|
+
|
|
2228
|
+
def get_value(self, context):
|
|
2229
|
+
dg_list_url = reverse("extras:dynamicgroup_list")
|
|
2230
|
+
job_run_url = reverse(
|
|
2231
|
+
"extras:job_run_by_class_path",
|
|
2232
|
+
kwargs={"class_path": "nautobot.core.jobs.groups.RefreshDynamicGroupCaches"},
|
|
2233
|
+
)
|
|
2234
|
+
return (
|
|
2235
|
+
"Dynamic group membership is cached for performance reasons, "
|
|
2236
|
+
"therefore this page may not always be up-to-date.\n\n"
|
|
2237
|
+
"You can refresh the membership of any specific group by accessing it from the list below or from the "
|
|
2238
|
+
f'[Dynamic Groups list view]({dg_list_url}) and clicking the "Refresh Members" button.\n\n'
|
|
2239
|
+
"You can also refresh the membership of **all** groups by running the "
|
|
2240
|
+
f"[Refresh Dynamic Group Caches job]({job_run_url})."
|
|
2241
|
+
)
|
|
2242
|
+
|
|
2243
|
+
|
|
2156
2244
|
@dataclass
|
|
2157
2245
|
class _ObjectDetailGroupsTab(Tab):
|
|
2158
2246
|
"""Built-in class for a Tab displaying information about associated dynamic groups."""
|
|
@@ -2169,8 +2257,9 @@ class _ObjectDetailGroupsTab(Tab):
|
|
|
2169
2257
|
):
|
|
2170
2258
|
if panels is None:
|
|
2171
2259
|
panels = (
|
|
2260
|
+
DynamicGroupsTextPanel(weight=100),
|
|
2172
2261
|
ObjectsTablePanel(
|
|
2173
|
-
weight=
|
|
2262
|
+
weight=200,
|
|
2174
2263
|
table_class=DynamicGroupTable,
|
|
2175
2264
|
table_attribute="dynamic_groups",
|
|
2176
2265
|
exclude_columns=["content_type"],
|
|
@@ -2227,8 +2316,7 @@ class _ObjectDetailMetadataTab(Tab):
|
|
|
2227
2316
|
exclude_columns=["assigned_object"],
|
|
2228
2317
|
add_button_route=None,
|
|
2229
2318
|
related_field_name="assigned_object_id",
|
|
2230
|
-
|
|
2231
|
-
label="Object Metadata",
|
|
2319
|
+
table_title="Object Metadata",
|
|
2232
2320
|
),
|
|
2233
2321
|
)
|
|
2234
2322
|
super().__init__(
|
nautobot/core/ui/titles.py
CHANGED
|
@@ -6,17 +6,14 @@ from django.utils.html import strip_tags
|
|
|
6
6
|
DEFAULT_TITLES: dict[str, str] = {
|
|
7
7
|
"*": "{{ verbose_name_plural|bettertitle }}",
|
|
8
8
|
"list": "{{ verbose_name_plural|bettertitle }}",
|
|
9
|
-
"detail": "{{ object.
|
|
10
|
-
"retrieve": "{{ object.
|
|
9
|
+
"detail": "{{ object.page_title|default:object }}",
|
|
10
|
+
"retrieve": "{{ object.page_title|default:object }}",
|
|
11
11
|
"destroy": "Delete {{ verbose_name }}?",
|
|
12
12
|
"create": "Add a new {{ verbose_name }}",
|
|
13
|
-
"update": "Editing {{ verbose_name }} {{ object.
|
|
13
|
+
"update": "Editing {{ verbose_name }} {{ object.page_title|default:object }}",
|
|
14
14
|
"bulk_destroy": "Delete {{ total_objs_to_delete }} {{ verbose_name_plural|bettertitle }}?",
|
|
15
15
|
"bulk_rename": "Renaming {{ selected_objects|length }} {{ verbose_name_plural|bettertitle }} on {{ parent_name }}",
|
|
16
16
|
"bulk_update": "Editing {{ objs_count }} {{ verbose_name_plural|bettertitle }}",
|
|
17
|
-
"changelog": "{{ object.display|default:object }} - Change Log",
|
|
18
|
-
"config_context": "{{ object.display|default:object }} - Config Context",
|
|
19
|
-
"notes": "{{ object.display|default:object }} - Notes",
|
|
20
17
|
"approve": "Approve {{ verbose_name|bettertitle }}?",
|
|
21
18
|
"deny": "Deny {{ verbose_name|bettertitle }}?",
|
|
22
19
|
}
|
nautobot/core/urls.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
2
|
from django.http import HttpResponse, HttpResponseNotFound
|
|
3
3
|
from django.urls import include, path
|
|
4
|
-
from django.views.generic import TemplateView
|
|
4
|
+
from django.views.generic import RedirectView, TemplateView
|
|
5
5
|
|
|
6
6
|
from nautobot.core.views import (
|
|
7
7
|
AboutView,
|
|
8
|
+
AppDocsView,
|
|
8
9
|
CustomGraphQLView,
|
|
9
10
|
get_file_with_authorization,
|
|
10
11
|
HomeView,
|
|
@@ -41,11 +42,13 @@ urlpatterns = [
|
|
|
41
42
|
path("dcim/", include("nautobot.dcim.urls")),
|
|
42
43
|
path("extras/", include("nautobot.extras.urls")),
|
|
43
44
|
path("ipam/", include("nautobot.ipam.urls")),
|
|
45
|
+
path("load-balancers/", include("nautobot.load_balancers.urls")),
|
|
44
46
|
path("tenancy/", include("nautobot.tenancy.urls")),
|
|
45
47
|
# TODO: deprecate this url and use users
|
|
46
48
|
path("user/", include("nautobot.users.urls")),
|
|
47
49
|
path("users/", include("nautobot.users.urls", "users")),
|
|
48
50
|
path("virtualization/", include("nautobot.virtualization.urls")),
|
|
51
|
+
path("vpn/", include("nautobot.vpn.urls")),
|
|
49
52
|
path("wireless/", include("nautobot.wireless.urls")),
|
|
50
53
|
# API
|
|
51
54
|
path("api/", include("nautobot.core.api.urls")),
|
|
@@ -59,6 +62,15 @@ urlpatterns = [
|
|
|
59
62
|
path("media-failure/", StaticMediaFailureView.as_view(), name="media_failure"),
|
|
60
63
|
# Apps
|
|
61
64
|
path("apps/", include((apps_patterns, "apps"))),
|
|
65
|
+
# Redirect /docs/<app_base_url>/ -> /docs/<app_base_url>/index.html
|
|
66
|
+
path(
|
|
67
|
+
"docs/<str:app_base_url>/",
|
|
68
|
+
RedirectView.as_view(pattern_name="docs_file", permanent=False),
|
|
69
|
+
kwargs={"path": "index.html"},
|
|
70
|
+
name="docs_index_redirect",
|
|
71
|
+
),
|
|
72
|
+
# Apps docs - Serve docs file
|
|
73
|
+
path("docs/<str:app_base_url>/<path:path>", AppDocsView.as_view(), name="docs_file"),
|
|
62
74
|
path("plugins/", include((plugin_patterns, "plugins"))),
|
|
63
75
|
path("admin/plugins/", include(plugin_admin_patterns)),
|
|
64
76
|
# Social auth/SSO
|
|
@@ -92,15 +104,14 @@ urlpatterns = [
|
|
|
92
104
|
|
|
93
105
|
|
|
94
106
|
if settings.DEBUG:
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
urlpatterns += [path("theme-preview/", ThemePreviewView.as_view(), name="theme_preview")]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if "debug_toolbar" in settings.INSTALLED_APPS:
|
|
111
|
+
from debug_toolbar.toolbar import debug_toolbar_urls
|
|
112
|
+
|
|
113
|
+
urlpatterns += debug_toolbar_urls()
|
|
97
114
|
|
|
98
|
-
urlpatterns += [
|
|
99
|
-
path("__debug__/", include(debug_toolbar.urls)),
|
|
100
|
-
path("theme-preview/", ThemePreviewView.as_view(), name="theme_preview"),
|
|
101
|
-
]
|
|
102
|
-
except ImportError:
|
|
103
|
-
pass
|
|
104
115
|
|
|
105
116
|
if settings.METRICS_ENABLED:
|
|
106
117
|
if settings.METRICS_AUTHENTICATED:
|
nautobot/core/utils/cache.py
CHANGED
|
@@ -66,5 +66,6 @@ def construct_cache_key(obj, *, method_name=None, branch_aware=True, **params):
|
|
|
66
66
|
if params_tokens:
|
|
67
67
|
cache_key += f"({','.join(params_tokens)})"
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
# Disabled as it's very noisy in some cases
|
|
70
|
+
# logger.debug("Constructed cache key is %s", cache_key)
|
|
70
71
|
return cache_key
|
nautobot/core/utils/filtering.py
CHANGED
|
@@ -17,6 +17,21 @@ from nautobot.core.utils.lookup import get_filterset_for_model
|
|
|
17
17
|
# e.g `name__ic` has lookup expr `ic (icontains)` while `name` has no lookup expr
|
|
18
18
|
CONTAINS_LOOKUP_EXPR_RE = re.compile(r"(?<=__)\w+")
|
|
19
19
|
|
|
20
|
+
MODEL_VERBOSE_NAME_PLURAL_TO_FEATURE_NAME_MAPPING = {
|
|
21
|
+
"approval_workflow_definitions": "approval_workflows",
|
|
22
|
+
"cables": "cable_terminations",
|
|
23
|
+
"data_compliance": "custom_validators",
|
|
24
|
+
"location_types": "locations",
|
|
25
|
+
"metadata_types": "metadata",
|
|
26
|
+
"min_max_validation_rules": "custom_validators",
|
|
27
|
+
"object_metadata": "metadata",
|
|
28
|
+
"regular_expression_validation_rules": "custom_validators",
|
|
29
|
+
"relationship_associations": "relationships",
|
|
30
|
+
"required_validation_rules": "custom_validators",
|
|
31
|
+
"static_group_associations": "dynamic_groups",
|
|
32
|
+
"unique_validation_rules": "custom_validators",
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
|
|
21
36
|
def build_lookup_label(field_name, _verbose_name):
|
|
22
37
|
"""
|
|
@@ -92,6 +107,7 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
|
|
|
92
107
|
BOOLEAN_CHOICES,
|
|
93
108
|
DynamicModelMultipleChoiceField,
|
|
94
109
|
MultipleContentTypeField,
|
|
110
|
+
MultiValueCharInput,
|
|
95
111
|
StaticSelect2,
|
|
96
112
|
StaticSelect2Multiple,
|
|
97
113
|
)
|
|
@@ -112,7 +128,16 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
|
|
|
112
128
|
elif isinstance(field, (MultiValueDecimalFilter, MultiValueFloatFilter)):
|
|
113
129
|
form_field = forms.DecimalField()
|
|
114
130
|
elif isinstance(field, NumberFilter):
|
|
115
|
-
|
|
131
|
+
# If "choices" are passed, then when 'exact' is used in an Advanced
|
|
132
|
+
# Filter, render a dropdown of choices instead of a free integer input
|
|
133
|
+
if field.lookup_expr == "exact" and getattr(field, "choices", None):
|
|
134
|
+
# Use a multi-value widget that allows both preset choices and free-form entries
|
|
135
|
+
form_field = forms.MultipleChoiceField(
|
|
136
|
+
choices=field.choices,
|
|
137
|
+
widget=MultiValueCharInput,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
form_field = forms.IntegerField()
|
|
116
141
|
elif isinstance(field, ModelMultipleChoiceFilter):
|
|
117
142
|
if getattr(field, "prefers_id", False):
|
|
118
143
|
to_field_name = "id"
|
|
@@ -131,26 +156,11 @@ def get_filterset_parameter_form_field(model, parameter, filterset=None):
|
|
|
131
156
|
elif isinstance(
|
|
132
157
|
field, ContentTypeMultipleChoiceFilter
|
|
133
158
|
): # While there are other objects using `ContentTypeMultipleChoiceFilter`, the case where
|
|
134
|
-
# models that have such a filter and the `verbose_name_plural`
|
|
159
|
+
# models that have such a filter and the `verbose_name_plural` does not match, we can lookup the feature name.
|
|
135
160
|
from nautobot.core.models.fields import slugify_dashes_to_underscores # Avoid circular import
|
|
136
161
|
|
|
137
162
|
plural_name = slugify_dashes_to_underscores(model._meta.verbose_name_plural)
|
|
138
|
-
|
|
139
|
-
# Cable-connectable models use "cable_terminations", not "cables", as the feature name
|
|
140
|
-
if plural_name == "cables":
|
|
141
|
-
plural_name = "cable_terminations"
|
|
142
|
-
elif plural_name == "metadata_types":
|
|
143
|
-
plural_name = "metadata"
|
|
144
|
-
elif plural_name == "object_metadata":
|
|
145
|
-
plural_name = "metadata"
|
|
146
|
-
elif plural_name in [
|
|
147
|
-
"data_compliance",
|
|
148
|
-
"min_max_validation_rules",
|
|
149
|
-
"regular_expression_validation_rules",
|
|
150
|
-
"required_validation_rules",
|
|
151
|
-
"unique_validation_rules",
|
|
152
|
-
]:
|
|
153
|
-
plural_name = "custom_validators"
|
|
163
|
+
plural_name = MODEL_VERBOSE_NAME_PLURAL_TO_FEATURE_NAME_MAPPING.get(plural_name, plural_name)
|
|
154
164
|
try:
|
|
155
165
|
form_field = MultipleContentTypeField(choices_as_strings=True, feature=plural_name)
|
|
156
166
|
except KeyError:
|