nautobot 2.4.17__py3-none-any.whl → 2.4.19__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.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/ui.py +6 -0
- nautobot/apps/views.py +2 -0
- nautobot/circuits/tables.py +1 -1
- nautobot/circuits/templates/circuits/circuit_create.html +7 -7
- nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -5
- nautobot/circuits/templates/circuits/circuittermination_create.html +26 -26
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +20 -20
- nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +3 -3
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +1 -3
- nautobot/circuits/tests/integration/test_circuit.py +2 -2
- nautobot/circuits/views.py +49 -15
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +1 -4
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +1 -7
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +1 -3
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +1 -5
- nautobot/cloud/views.py +45 -0
- nautobot/core/filters.py +2 -2
- nautobot/core/graphql/generators.py +5 -2
- nautobot/core/jobs/bulk_actions.py +48 -85
- nautobot/core/models/querysets.py +2 -1
- nautobot/core/settings.py +1 -0
- nautobot/core/settings.yaml +9 -0
- nautobot/core/tables.py +21 -23
- nautobot/core/templates/40x.html +15 -15
- nautobot/core/templates/500.html +21 -21
- nautobot/core/templates/admin/app_index.html +8 -8
- nautobot/core/templates/admin/base.html +104 -104
- nautobot/core/templates/admin/change_form.html +65 -65
- nautobot/core/templates/admin/change_list.html +60 -60
- nautobot/core/templates/admin/change_list_results.html +39 -39
- nautobot/core/templates/admin/config/config.html +47 -47
- nautobot/core/templates/admin/delete_confirmation.html +47 -47
- nautobot/core/templates/admin/edit_inline/stacked.html +124 -124
- nautobot/core/templates/admin/edit_inline/tabular.html +60 -60
- nautobot/core/templates/admin/includes/fieldset.html +4 -4
- nautobot/core/templates/admin/index.html +60 -60
- nautobot/core/templates/admin/prepopulated_fields_js.html +18 -18
- nautobot/core/templates/admin/submit_line.html +4 -4
- nautobot/core/templates/base_django.html +46 -46
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +8 -8
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +8 -8
- nautobot/core/templates/buttons/export.html +3 -3
- nautobot/core/templates/components/breadcrumbs.html +19 -0
- nautobot/core/templates/components/button/default.html +3 -3
- nautobot/core/templates/components/button/dropdown.html +7 -7
- nautobot/core/templates/components/button/formbutton.html +4 -4
- nautobot/core/templates/components/panel/body_content_data_table.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +3 -0
- nautobot/core/templates/components/panel/footer_content_table.html +3 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +10 -1
- nautobot/core/templates/components/tab/content_wrapper.html +1 -1
- nautobot/core/templates/components/tab/label_wrapper.html +1 -1
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +10 -3
- nautobot/core/templates/generic/object_bulk_add_component.html +40 -40
- nautobot/core/templates/generic/object_bulk_create.html +3 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +6 -6
- nautobot/core/templates/generic/object_bulk_update.html +52 -52
- nautobot/core/templates/generic/object_changelog.html +0 -2
- nautobot/core/templates/generic/object_import.html +33 -33
- nautobot/core/templates/generic/object_list.html +271 -268
- nautobot/core/templates/generic/object_notes.html +0 -2
- nautobot/core/templates/generic/object_retrieve.html +264 -257
- nautobot/core/templates/graphene/graphiql.html +127 -127
- nautobot/core/templates/home.html +62 -62
- nautobot/core/templates/inc/computed_fields/panel_data.html +13 -13
- nautobot/core/templates/inc/created_updated.html +8 -8
- nautobot/core/templates/inc/custom_fields/panel_data.html +13 -13
- nautobot/core/templates/inc/dynamic_groups_panel.html +11 -11
- nautobot/core/templates/inc/footer.html +19 -19
- nautobot/core/templates/inc/javascript.html +1 -1
- nautobot/core/templates/inc/media.html +46 -46
- nautobot/core/templates/inc/nav_menu.html +1 -1
- nautobot/core/templates/inc/relationships_table_rows.html +22 -22
- nautobot/core/templates/inc/tenant_table_row.html +1 -1
- nautobot/core/templates/login.html +77 -77
- nautobot/core/templates/media_failure.html +38 -38
- nautobot/core/templates/panel_table.html +1 -1
- nautobot/core/templates/rest_framework/api.html +3 -3
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +9 -9
- nautobot/core/templates/utilities/confirmation_form.html +18 -18
- nautobot/core/templates/utilities/render_field.html +1 -1
- nautobot/core/templates/utilities/render_jinja2.html +43 -43
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +56 -56
- nautobot/core/templates/utilities/templatetags/utilization_graph.html +1 -1
- nautobot/core/templates/utilities/theme_preview.html +799 -799
- nautobot/core/templates/utilities/worker_status.html +122 -122
- nautobot/core/templates/widgets/clearable_file.html +3 -3
- nautobot/core/templates/widgets/sluginput.html +1 -1
- nautobot/core/templatetags/buttons.py +8 -2
- nautobot/core/templatetags/helpers.py +24 -0
- nautobot/core/templatetags/ui_framework.py +40 -5
- nautobot/core/testing/filters.py +37 -21
- nautobot/core/testing/integration.py +7 -4
- nautobot/core/testing/views.py +49 -5
- nautobot/core/tests/test_breadcrumbs.py +78 -4
- nautobot/core/tests/test_commands.py +7 -4
- nautobot/core/tests/test_graphql.py +20 -5
- nautobot/core/tests/test_jobs.py +34 -21
- nautobot/core/tests/test_tables.py +43 -6
- nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
- nautobot/core/tests/test_titles.py +2 -2
- nautobot/core/tests/test_ui.py +188 -1
- nautobot/core/tests/test_utils.py +35 -0
- nautobot/core/tests/test_views.py +45 -0
- nautobot/core/tests/test_views_generic.py +43 -0
- nautobot/core/tests/test_views_utils.py +239 -5
- nautobot/core/ui/breadcrumbs.py +220 -28
- nautobot/core/ui/bulk_buttons.py +8 -0
- nautobot/core/ui/object_detail.py +181 -60
- nautobot/core/ui/titles.py +10 -5
- nautobot/core/utils/requests.py +27 -2
- nautobot/core/views/__init__.py +24 -3
- nautobot/core/views/generic.py +70 -35
- nautobot/core/views/mixins.py +226 -122
- nautobot/core/views/utils.py +270 -1
- nautobot/dcim/api/serializers.py +8 -2
- nautobot/dcim/constants.py +1 -0
- nautobot/dcim/factory.py +4 -3
- nautobot/dcim/filters/mixins.py +1 -2
- nautobot/dcim/forms.py +5 -1
- nautobot/dcim/migrations/0074_alter_rack_u_height.py +21 -0
- nautobot/dcim/models/devices.py +30 -1
- nautobot/dcim/models/racks.py +2 -2
- nautobot/dcim/tables/__init__.py +2 -0
- nautobot/dcim/tables/devices.py +24 -0
- nautobot/dcim/tables/power.py +2 -2
- nautobot/dcim/templates/dcim/cable.html +53 -53
- nautobot/dcim/templates/dcim/cable_connect.html +182 -182
- nautobot/dcim/templates/dcim/cable_trace.html +1 -1
- nautobot/dcim/templates/dcim/console_port_connection_list.html +5 -5
- nautobot/dcim/templates/dcim/consoleport.html +86 -86
- nautobot/dcim/templates/dcim/consoleserverport.html +86 -86
- nautobot/dcim/templates/dcim/controller_create.html +34 -34
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -68
- nautobot/dcim/templates/dcim/device/base.html +1 -114
- nautobot/dcim/templates/dcim/device/config.html +17 -17
- nautobot/dcim/templates/dcim/device/consoleports.html +1 -52
- nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -52
- nautobot/dcim/templates/dcim/device/devicebays.html +1 -48
- nautobot/dcim/templates/dcim/device/frontports.html +1 -52
- nautobot/dcim/templates/dcim/device/interfaces.html +1 -56
- nautobot/dcim/templates/dcim/device/inventory.html +1 -48
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +64 -64
- nautobot/dcim/templates/dcim/device/modulebays.html +1 -48
- nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -52
- nautobot/dcim/templates/dcim/device/powerports.html +1 -52
- nautobot/dcim/templates/dcim/device/rearports.html +1 -52
- nautobot/dcim/templates/dcim/device/status.html +66 -66
- nautobot/dcim/templates/dcim/device/wireless.html +1 -72
- nautobot/dcim/templates/dcim/device.html +4 -422
- nautobot/dcim/templates/dcim/device_component.html +0 -19
- nautobot/dcim/templates/dcim/device_component_add.html +25 -25
- nautobot/dcim/templates/dcim/device_create.html +229 -0
- nautobot/dcim/templates/dcim/device_edit.html +2 -227
- nautobot/dcim/templates/dcim/devicebay.html +41 -41
- nautobot/dcim/templates/dcim/devicebay_populate.html +32 -32
- nautobot/dcim/templates/dcim/devicetype_component_add.html +28 -28
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -3
- nautobot/dcim/templates/dcim/frontport.html +84 -84
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
- nautobot/dcim/templates/dcim/inc/device_interface_filter.html +8 -0
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +1 -15
- nautobot/dcim/templates/dcim/inc/location_hierarchy.html +22 -22
- nautobot/dcim/templates/dcim/interface.html +206 -206
- nautobot/dcim/templates/dcim/interface_connection_list.html +5 -5
- nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +6 -6
- nautobot/dcim/templates/dcim/inventoryitem.html +44 -44
- nautobot/dcim/templates/dcim/inventoryitem_add.html +32 -32
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +22 -22
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +46 -46
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -7
- nautobot/dcim/templates/dcim/locationtype.html +1 -6
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -7
- nautobot/dcim/templates/dcim/module/base.html +85 -85
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_retrieve.html +52 -52
- nautobot/dcim/templates/dcim/module_update.html +61 -61
- nautobot/dcim/templates/dcim/modulebay_destroy.html +1 -1
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +83 -99
- nautobot/dcim/templates/dcim/modulebay_update.html +33 -33
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +140 -144
- nautobot/dcim/templates/dcim/platform_create.html +38 -38
- nautobot/dcim/templates/dcim/power_port_connection_list.html +5 -5
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -8
- nautobot/dcim/templates/dcim/poweroutlet.html +85 -85
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +1 -8
- nautobot/dcim/templates/dcim/powerport.html +91 -91
- nautobot/dcim/templates/dcim/rack_elevation_list.html +18 -18
- nautobot/dcim/templates/dcim/rack_retrieve.html +264 -274
- nautobot/dcim/templates/dcim/rackreservation_retrieve.html +0 -3
- nautobot/dcim/templates/dcim/rearport.html +78 -78
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -5
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +3 -2
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +6 -2
- nautobot/dcim/tests/test_api.py +33 -1
- nautobot/dcim/tests/test_views.py +189 -4
- nautobot/dcim/ui.py +29 -0
- nautobot/dcim/urls.py +1 -109
- nautobot/dcim/utils.py +30 -0
- nautobot/dcim/views.py +1149 -550
- nautobot/extras/filters/mixins.py +1 -1
- nautobot/extras/forms/forms.py +15 -0
- nautobot/extras/models/groups.py +10 -1
- nautobot/extras/models/jobs.py +2 -2
- nautobot/extras/plugins/views.py +18 -5
- nautobot/extras/tables.py +24 -2
- nautobot/extras/templates/extras/computedfield_edit.html +4 -4
- nautobot/extras/templates/extras/configcontext_update.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +32 -32
- nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
- nautobot/extras/templates/extras/customfield_update.html +23 -23
- nautobot/extras/templates/extras/dynamicgroup.html +2 -99
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
- nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
- nautobot/extras/templates/extras/gitrepository.html +2 -82
- nautobot/extras/templates/extras/gitrepository_list.html +10 -10
- nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
- nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
- nautobot/extras/templates/extras/gitrepository_update.html +13 -0
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +73 -73
- nautobot/extras/templates/extras/inc/configcontext_format.html +2 -2
- nautobot/extras/templates/extras/inc/job_table.html +10 -10
- nautobot/extras/templates/extras/inc/jobresult.html +21 -21
- nautobot/extras/templates/extras/inc/jobresult_js.html +6 -6
- nautobot/extras/templates/extras/inc/tags_panel.html +10 -10
- nautobot/extras/templates/extras/job.html +64 -64
- nautobot/extras/templates/extras/job_approval_request.html +9 -9
- nautobot/extras/templates/extras/job_bulk_edit.html +13 -13
- nautobot/extras/templates/extras/job_edit.html +45 -45
- nautobot/extras/templates/extras/job_list.html +4 -4
- nautobot/extras/templates/extras/jobresult_retrieve.html +0 -25
- nautobot/extras/templates/extras/marketplace.html +101 -101
- nautobot/extras/templates/extras/metadatatype_create.html +20 -20
- nautobot/extras/templates/extras/note_retrieve.html +0 -52
- nautobot/extras/templates/extras/object_assign_contact_or_team.html +18 -18
- nautobot/extras/templates/extras/object_configcontext.html +1 -3
- nautobot/extras/templates/extras/objectchange.html +2 -165
- nautobot/extras/templates/extras/objectchange_retrieve.html +165 -0
- nautobot/extras/templates/extras/plugin_detail.html +44 -48
- nautobot/extras/templates/extras/plugins_list.html +9 -11
- nautobot/extras/templates/extras/plugins_tiles.html +26 -26
- nautobot/extras/templates/extras/relationship_edit.html +4 -4
- nautobot/extras/templates/extras/role_retrieve.html +13 -13
- nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +21 -21
- nautobot/extras/templates/extras/scheduledjob.html +128 -128
- nautobot/extras/templates/extras/secret_create.html +53 -53
- nautobot/extras/templates/extras/secretsgroup_update.html +13 -13
- nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +3 -3
- nautobot/extras/templates/extras/webhook.html +79 -79
- nautobot/extras/tests/integration/test_relationships.py +6 -6
- nautobot/extras/tests/test_dynamicgroups.py +73 -18
- nautobot/extras/tests/test_filters.py +1 -1
- nautobot/extras/tests/test_jobs.py +2 -0
- nautobot/extras/tests/test_views.py +8 -3
- nautobot/extras/urls.py +3 -97
- nautobot/extras/views.py +524 -456
- nautobot/ipam/filters.py +2 -2
- nautobot/ipam/migrations/0053_alter_vrfdeviceassignment_options_and_more.py +20 -0
- nautobot/ipam/models.py +34 -0
- nautobot/ipam/querysets.py +3 -3
- nautobot/ipam/signals.py +6 -1
- nautobot/ipam/tables.py +3 -1
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +4 -0
- nautobot/ipam/templates/ipam/inc/toggle_available.html +8 -8
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +4 -4
- nautobot/ipam/templates/ipam/ipaddress.html +119 -123
- nautobot/ipam/templates/ipam/ipaddress_assign.html +10 -10
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_merge.html +180 -180
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +48 -48
- nautobot/ipam/templates/ipam/prefix.html +2 -115
- nautobot/ipam/templates/ipam/prefix_create.html +34 -0
- nautobot/ipam/templates/ipam/prefix_edit.html +1 -34
- nautobot/ipam/templates/ipam/prefix_retrieve.html +3 -0
- nautobot/ipam/templates/ipam/service_retrieve.html +1 -6
- nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -7
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -1
- nautobot/ipam/tests/test_api.py +5 -0
- nautobot/ipam/tests/test_models.py +387 -0
- nautobot/ipam/tests/test_querysets.py +46 -0
- nautobot/ipam/tests/test_views.py +34 -0
- nautobot/ipam/ui.py +145 -0
- nautobot/ipam/urls.py +1 -46
- nautobot/ipam/utils/__init__.py +26 -0
- nautobot/ipam/utils/migrations.py +1 -1
- nautobot/ipam/views.py +234 -112
- nautobot/project-static/docs/404.html +11 -11
- nautobot/project-static/docs/apps/index.html +11 -11
- nautobot/project-static/docs/apps/nautobot-apps.html +11 -11
- nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js → bundle.f55a23d4.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.92b07e13.min.js.map → bundle.f55a23d4.min.js.map} +2 -2
- nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css → main.e53b48f4.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.7e37652d.min.css.map → main.e53b48f4.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +83 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1265 -281
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +11 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +12 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +452 -29
- nautobot/project-static/docs/development/apps/api/configuration-view.html +11 -11
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -11
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +11 -11
- nautobot/project-static/docs/development/apps/api/models/global-search.html +11 -11
- nautobot/project-static/docs/development/apps/api/models/graphql.html +11 -11
- nautobot/project-static/docs/development/apps/api/models/index.html +11 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -12
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +11 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +11 -11
- nautobot/project-static/docs/development/apps/api/prometheus.html +11 -11
- nautobot/project-static/docs/development/apps/api/setup.html +11 -11
- nautobot/project-static/docs/development/apps/api/testing.html +11 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +11 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +11 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +11 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +11 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/base-template.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/index.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/notes.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +11 -11
- nautobot/project-static/docs/development/apps/api/views/urls.html +11 -11
- nautobot/project-static/docs/development/apps/index.html +11 -11
- nautobot/project-static/docs/development/apps/migration/code-updates.html +11 -11
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +11 -11
- nautobot/project-static/docs/development/apps/migration/from-v1.html +11 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +11 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +11 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +11 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +11 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +11 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +11 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +11 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +11 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +11 -11
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +11 -11
- nautobot/project-static/docs/development/core/application-registry.html +11 -11
- nautobot/project-static/docs/development/core/best-practices.html +11 -11
- nautobot/project-static/docs/development/core/bootstrap-ui.html +11 -11
- nautobot/project-static/docs/development/core/caching.html +11 -11
- nautobot/project-static/docs/development/core/controllers.html +11 -11
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +11 -11
- nautobot/project-static/docs/development/core/generic-views.html +11 -11
- nautobot/project-static/docs/development/core/getting-started.html +50 -63
- nautobot/project-static/docs/development/core/homepage.html +11 -11
- nautobot/project-static/docs/development/core/index.html +11 -11
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +11 -11
- nautobot/project-static/docs/development/core/model-checklist.html +11 -11
- nautobot/project-static/docs/development/core/model-features.html +11 -11
- nautobot/project-static/docs/development/core/natural-keys.html +11 -11
- nautobot/project-static/docs/development/core/navigation-menu.html +11 -11
- nautobot/project-static/docs/development/core/release-checklist.html +11 -11
- nautobot/project-static/docs/development/core/role-internals.html +11 -11
- nautobot/project-static/docs/development/core/settings.html +11 -11
- nautobot/project-static/docs/development/core/style-guide.html +15 -11
- nautobot/project-static/docs/development/core/templates.html +11 -11
- nautobot/project-static/docs/development/core/testing.html +11 -11
- nautobot/project-static/docs/development/core/ui-component-framework.html +17 -22
- nautobot/project-static/docs/development/core/user-preferences.html +11 -11
- nautobot/project-static/docs/development/index.html +11 -11
- nautobot/project-static/docs/development/jobs/getting-started.html +11 -11
- nautobot/project-static/docs/development/jobs/index.html +11 -11
- nautobot/project-static/docs/development/jobs/installation.html +11 -11
- nautobot/project-static/docs/development/jobs/job-extensions.html +11 -11
- nautobot/project-static/docs/development/jobs/job-logging.html +11 -11
- nautobot/project-static/docs/development/jobs/job-patterns.html +11 -11
- nautobot/project-static/docs/development/jobs/job-structure.html +11 -11
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +11 -11
- nautobot/project-static/docs/development/jobs/testing.html +11 -11
- nautobot/project-static/docs/index.html +11 -11
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +11 -11
- nautobot/project-static/docs/overview/design_philosophy.html +11 -11
- nautobot/project-static/docs/release-notes/index.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.0.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.1.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.2.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.3.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.4.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.5.html +11 -11
- nautobot/project-static/docs/release-notes/version-1.6.html +11 -11
- nautobot/project-static/docs/release-notes/version-2.0.html +11 -11
- nautobot/project-static/docs/release-notes/version-2.1.html +11 -11
- nautobot/project-static/docs/release-notes/version-2.2.html +11 -11
- nautobot/project-static/docs/release-notes/version-2.3.html +11 -11
- nautobot/project-static/docs/release-notes/version-2.4.html +418 -11
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +300 -300
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +11 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +11 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +11 -11
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +11 -11
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +11 -11
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -11
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +89 -14
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +11 -11
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/index.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +11 -11
- nautobot/project-static/docs/user-guide/administration/installation/services.html +11 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +11 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +11 -11
- nautobot/project-static/docs/user-guide/administration/security/index.html +11 -11
- nautobot/project-static/docs/user-guide/administration/security/notices.html +11 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +11 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +11 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +11 -11
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +11 -11
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +11 -11
- nautobot/project-static/docs/user-guide/index.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +11 -11
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +11 -11
- nautobot/project-static/img/nautobot_icon.svg +32 -34
- nautobot/project-static/js/table_sorting_indicator.js +0 -2
- nautobot/tenancy/templates/tenancy/tenant.html +1 -7
- nautobot/tenancy/views.py +13 -0
- nautobot/users/templates/users/api_tokens.html +4 -4
- nautobot/users/templates/users/base.html +28 -28
- nautobot/virtualization/templates/virtualization/cluster.html +64 -64
- nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +8 -0
- nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +25 -25
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +1 -251
- nautobot/virtualization/templates/virtualization/vminterface.html +70 -70
- nautobot/virtualization/urls.py +0 -12
- nautobot/virtualization/views.py +158 -54
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +13 -13
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -1
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/METADATA +4 -4
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/RECORD +623 -607
- nautobot/core/templates/inc/breadcrumbs.html +0 -14
- nautobot/ipam/templates/ipam/prefix_ipaddresses.html +0 -11
- nautobot/ipam/templates/ipam/prefix_prefixes.html +0 -11
- nautobot/project-static/docs/requirements.txt +0 -14
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/NOTICE +0 -0
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/WHEEL +0 -0
- {nautobot-2.4.17.dist-info → nautobot-2.4.19.dist-info}/entry_points.txt +0 -0
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import urllib.parse
|
|
2
2
|
|
|
3
3
|
from django.contrib.auth.models import AnonymousUser
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
4
5
|
from django.db import ProgrammingError
|
|
5
|
-
from django.test import TestCase
|
|
6
6
|
|
|
7
7
|
from nautobot.core.models.querysets import count_related
|
|
8
|
-
from nautobot.core.testing import
|
|
9
|
-
from nautobot.core.views.utils import
|
|
8
|
+
from nautobot.core.testing import TestCase
|
|
9
|
+
from nautobot.core.views.utils import (
|
|
10
|
+
check_filter_for_display,
|
|
11
|
+
get_bulk_queryset_from_view,
|
|
12
|
+
get_saved_views_for_user,
|
|
13
|
+
prepare_cloned_fields,
|
|
14
|
+
)
|
|
10
15
|
from nautobot.dcim.filters import DeviceFilterSet
|
|
11
16
|
from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, InventoryItem, Location, Manufacturer
|
|
12
17
|
from nautobot.extras.models import Role, SavedView, Status
|
|
13
|
-
from nautobot.
|
|
18
|
+
from nautobot.ipam.models import Namespace, VRF
|
|
19
|
+
from nautobot.users.models import ObjectPermission, User
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
class CheckFilterForDisplayTest(TestCase):
|
|
@@ -173,7 +179,7 @@ class CheckPrepareClonedFields(TestCase):
|
|
|
173
179
|
self.assertTrue(query_params["description"][0] == description)
|
|
174
180
|
|
|
175
181
|
|
|
176
|
-
class GetSavedViewsForUserTestCase(
|
|
182
|
+
class GetSavedViewsForUserTestCase(TestCase):
|
|
177
183
|
"""
|
|
178
184
|
Class to test `get_saved_views_for_user`.
|
|
179
185
|
"""
|
|
@@ -186,6 +192,8 @@ class GetSavedViewsForUserTestCase(TransactionTestCase):
|
|
|
186
192
|
|
|
187
193
|
def setUp(self):
|
|
188
194
|
super().setUp()
|
|
195
|
+
# We want a clean slate for SavedViews
|
|
196
|
+
SavedView.objects.all().delete()
|
|
189
197
|
self.user2 = User.objects.create_user(username="second_user")
|
|
190
198
|
self.create_saved_view(name="saved_view")
|
|
191
199
|
self.create_saved_view(name="saved_view_shared", is_shared=True)
|
|
@@ -219,3 +227,229 @@ class GetSavedViewsForUserTestCase(TransactionTestCase):
|
|
|
219
227
|
self.assertEqual(saved_views.count(), 2)
|
|
220
228
|
expected_names = ["saved_view_shared", "saved_view_shared_different_owner"]
|
|
221
229
|
self.assertEqual(list(saved_views.values_list("name", flat=True)), expected_names)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class GetBulkQuerysetFromViewTestCase(TestCase):
|
|
233
|
+
"""Class to test get_bulk_queryset_from_view with VRF."""
|
|
234
|
+
|
|
235
|
+
def setUp(self):
|
|
236
|
+
super().setUp()
|
|
237
|
+
self.user = User.objects.create_user(username="testuser", password="testpass", is_superuser=True) # noqa: S106 # hardcoded-password-func-arg -- ok as this is test code only
|
|
238
|
+
self.vrfs = []
|
|
239
|
+
namespace = Namespace.objects.create(name="test-namespace")
|
|
240
|
+
for i in range(10):
|
|
241
|
+
vrf = VRF.objects.create(
|
|
242
|
+
name=f"VRF {i}",
|
|
243
|
+
description=f"desc-{i % 3}", # 3 unique descriptions, repeated
|
|
244
|
+
namespace=namespace,
|
|
245
|
+
)
|
|
246
|
+
self.vrfs.append(vrf)
|
|
247
|
+
# SavedView filters by name of VRF 5
|
|
248
|
+
self.saved_view = SavedView.objects.create(
|
|
249
|
+
name="VRF 5 Only",
|
|
250
|
+
owner=self.user,
|
|
251
|
+
view="ipam:vrf_list",
|
|
252
|
+
config={"filter_params": {"name": self.vrfs[5].name}},
|
|
253
|
+
)
|
|
254
|
+
self.saved_view_empty = SavedView.objects.create(
|
|
255
|
+
name="NO Filter Params", owner=self.user, view="ipam:vrf_list", config={"per_page": 50, "sort_order": []}
|
|
256
|
+
)
|
|
257
|
+
self.content_type = ContentType.objects.get_for_model(VRF)
|
|
258
|
+
|
|
259
|
+
def test_not_is_all_and_pk_list(self):
|
|
260
|
+
"""!is_all and pk_list: Return queryset filtered by pk_list"""
|
|
261
|
+
qs = get_bulk_queryset_from_view(
|
|
262
|
+
user=self.user,
|
|
263
|
+
content_type=self.content_type,
|
|
264
|
+
edit_all=False,
|
|
265
|
+
filter_query_params={},
|
|
266
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk],
|
|
267
|
+
saved_view_id=None,
|
|
268
|
+
action="change",
|
|
269
|
+
)
|
|
270
|
+
self.assertQuerysetEqual(qs, [self.vrfs[2], self.vrfs[4]], ordered=False)
|
|
271
|
+
|
|
272
|
+
def test_not_is_all_and_no_pk_list(self):
|
|
273
|
+
"""!is_all and !pk_list: Return empty queryset. This should not normally happen in practice."""
|
|
274
|
+
qs = get_bulk_queryset_from_view(
|
|
275
|
+
user=self.user,
|
|
276
|
+
content_type=self.content_type,
|
|
277
|
+
edit_all=False,
|
|
278
|
+
filter_query_params={},
|
|
279
|
+
pk_list=[],
|
|
280
|
+
saved_view_id=None,
|
|
281
|
+
action="change",
|
|
282
|
+
)
|
|
283
|
+
self.assertEqual(qs.count(), 0)
|
|
284
|
+
|
|
285
|
+
def test_is_all_and_no_saved_view_and_no_filter_query_params(self):
|
|
286
|
+
"""is_all and !saved_view_id and !filter_query_params: Return all objects"""
|
|
287
|
+
qs = get_bulk_queryset_from_view(
|
|
288
|
+
user=self.user,
|
|
289
|
+
content_type=self.content_type,
|
|
290
|
+
edit_all=True,
|
|
291
|
+
filter_query_params={},
|
|
292
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
293
|
+
saved_view_id=None,
|
|
294
|
+
action="change",
|
|
295
|
+
)
|
|
296
|
+
self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
|
|
297
|
+
|
|
298
|
+
def test_all_filters_removed_flag_ignores_saved_view(self):
|
|
299
|
+
"""
|
|
300
|
+
If filter_query_params contains 'all_filters_removed', the saved view is ignored and all objects are returned.
|
|
301
|
+
"""
|
|
302
|
+
# Pass the all_filters_removed flag in filter_query_params
|
|
303
|
+
qs = get_bulk_queryset_from_view(
|
|
304
|
+
user=self.user,
|
|
305
|
+
content_type=self.content_type,
|
|
306
|
+
filter_query_params={"all_filters_removed": [True], "saved_view": [self.saved_view.id]},
|
|
307
|
+
pk_list=[],
|
|
308
|
+
saved_view_id=self.saved_view.id,
|
|
309
|
+
action="change",
|
|
310
|
+
edit_all=True,
|
|
311
|
+
)
|
|
312
|
+
# Should return all VRFs, not just the one from the saved view
|
|
313
|
+
self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
|
|
314
|
+
|
|
315
|
+
def test_is_all_and_filter_query_params(self):
|
|
316
|
+
"""is_all and filter_query_params: Return queryset filtered by filter_query_params (description)"""
|
|
317
|
+
qs = get_bulk_queryset_from_view(
|
|
318
|
+
user=self.user,
|
|
319
|
+
content_type=self.content_type,
|
|
320
|
+
edit_all=True,
|
|
321
|
+
filter_query_params={"description": ["desc-1"]},
|
|
322
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
323
|
+
saved_view_id=None,
|
|
324
|
+
action="change",
|
|
325
|
+
)
|
|
326
|
+
self.assertQuerysetEqual(qs, VRF.objects.filter(description="desc-1"), ordered=False)
|
|
327
|
+
|
|
328
|
+
def test_is_all_and_saved_view_id(self):
|
|
329
|
+
"""is_all and saved_view_id: Return queryset filtered by saved_view_filter_params (name)"""
|
|
330
|
+
qs = get_bulk_queryset_from_view(
|
|
331
|
+
user=self.user,
|
|
332
|
+
content_type=self.content_type,
|
|
333
|
+
edit_all=True,
|
|
334
|
+
filter_query_params={},
|
|
335
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
336
|
+
saved_view_id=self.saved_view.id,
|
|
337
|
+
action="change",
|
|
338
|
+
)
|
|
339
|
+
self.assertQuerysetEqual(qs, [self.vrfs[5]], ordered=False)
|
|
340
|
+
|
|
341
|
+
def test_is_all_and_not_saved_view_id_but_saved_view_filter_params(self):
|
|
342
|
+
"""is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
|
|
343
|
+
qs = get_bulk_queryset_from_view(
|
|
344
|
+
user=self.user,
|
|
345
|
+
content_type=self.content_type,
|
|
346
|
+
edit_all=True,
|
|
347
|
+
filter_query_params={"name": [self.vrfs[7].name]},
|
|
348
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
349
|
+
saved_view_id=None,
|
|
350
|
+
action="change",
|
|
351
|
+
)
|
|
352
|
+
self.assertQuerysetEqual(qs, [self.vrfs[7]], ordered=False)
|
|
353
|
+
|
|
354
|
+
def test_queryset_respects_permissions(self):
|
|
355
|
+
"""is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
|
|
356
|
+
# Create a non-superuser with no permissions
|
|
357
|
+
limited_permissions_user = User.objects.create_user(
|
|
358
|
+
username="user_no_perms",
|
|
359
|
+
password="testpass", # noqa: S106 # hardcoded-password-func-arg -- ok as this is test code only
|
|
360
|
+
is_superuser=False,
|
|
361
|
+
)
|
|
362
|
+
# Assign object permission
|
|
363
|
+
obj_perm = ObjectPermission.objects.create(
|
|
364
|
+
name="Test permission",
|
|
365
|
+
constraints={"name": self.vrfs[2].name},
|
|
366
|
+
actions=["view", "change", "delete", "add"],
|
|
367
|
+
)
|
|
368
|
+
obj_perm.users.add(limited_permissions_user)
|
|
369
|
+
obj_perm.object_types.add(self.content_type)
|
|
370
|
+
|
|
371
|
+
qs = get_bulk_queryset_from_view(
|
|
372
|
+
user=limited_permissions_user,
|
|
373
|
+
content_type=self.content_type,
|
|
374
|
+
edit_all=True,
|
|
375
|
+
filter_query_params={},
|
|
376
|
+
pk_list=[self.vrfs[3].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
377
|
+
saved_view_id=None,
|
|
378
|
+
action="change",
|
|
379
|
+
)
|
|
380
|
+
self.assertQuerysetEqual(qs, VRF.objects.filter(name=self.vrfs[2].name), ordered=False)
|
|
381
|
+
|
|
382
|
+
def test_querydict_ignores(self):
|
|
383
|
+
"""is_all and not saved_view_id: Return queryset filtered by filter_query_params (name)"""
|
|
384
|
+
filter_query_params = {"name": [self.vrfs[7].name], "per_page": [10]}
|
|
385
|
+
qs = get_bulk_queryset_from_view(
|
|
386
|
+
user=self.user,
|
|
387
|
+
content_type=self.content_type,
|
|
388
|
+
edit_all=True,
|
|
389
|
+
filter_query_params=filter_query_params,
|
|
390
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
391
|
+
saved_view_id=None,
|
|
392
|
+
action="change",
|
|
393
|
+
)
|
|
394
|
+
self.assertQuerysetEqual(qs, VRF.objects.filter(name=self.vrfs[7].name), ordered=False)
|
|
395
|
+
|
|
396
|
+
def test_no_valid_operation_found_raises(self):
|
|
397
|
+
"""If no valid operation is found, raise RuntimeError."""
|
|
398
|
+
# Simulate a situation where all logic branches are skipped
|
|
399
|
+
with self.assertRaises(RuntimeError):
|
|
400
|
+
get_bulk_queryset_from_view(
|
|
401
|
+
user=self.user,
|
|
402
|
+
content_type=self.content_type,
|
|
403
|
+
filter_query_params=None,
|
|
404
|
+
pk_list=[],
|
|
405
|
+
saved_view_id=None,
|
|
406
|
+
action="change",
|
|
407
|
+
edit_all=None, # Not True, so not valid for 'change'
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def test_runtime_error_when_all_param_missing(self):
|
|
411
|
+
"""Raise RuntimeError if required *_all param is not provided for the action."""
|
|
412
|
+
with self.assertRaises(RuntimeError):
|
|
413
|
+
get_bulk_queryset_from_view(
|
|
414
|
+
user=self.user,
|
|
415
|
+
content_type=self.content_type,
|
|
416
|
+
filter_query_params={},
|
|
417
|
+
pk_list=[],
|
|
418
|
+
saved_view_id=None,
|
|
419
|
+
action="delete", # delete_all is missing
|
|
420
|
+
)
|
|
421
|
+
with self.assertRaises(RuntimeError):
|
|
422
|
+
get_bulk_queryset_from_view(
|
|
423
|
+
user=self.user,
|
|
424
|
+
content_type=self.content_type,
|
|
425
|
+
filter_query_params={},
|
|
426
|
+
pk_list=[],
|
|
427
|
+
saved_view_id=None,
|
|
428
|
+
action="change", # edit_all is missing
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
def test_is_all_and_saved_view__with_no_filter_params(self):
|
|
432
|
+
"""is_all and saved_view_id with no filter_params in saved view: Return all objects"""
|
|
433
|
+
qs = get_bulk_queryset_from_view(
|
|
434
|
+
user=self.user,
|
|
435
|
+
content_type=self.content_type,
|
|
436
|
+
edit_all=True,
|
|
437
|
+
filter_query_params={},
|
|
438
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
439
|
+
saved_view_id=self.saved_view_empty.id,
|
|
440
|
+
action="change",
|
|
441
|
+
)
|
|
442
|
+
self.assertQuerysetEqual(qs, VRF.objects.all(), ordered=False)
|
|
443
|
+
|
|
444
|
+
def test_bad_saved_view(self):
|
|
445
|
+
"""Test with a saved view ID that does not exist: Return no objects"""
|
|
446
|
+
qs = get_bulk_queryset_from_view(
|
|
447
|
+
user=self.user,
|
|
448
|
+
content_type=self.content_type,
|
|
449
|
+
edit_all=True,
|
|
450
|
+
filter_query_params={},
|
|
451
|
+
pk_list=[self.vrfs[2].pk, self.vrfs[4].pk], # should be ignored but is sent anyway by form
|
|
452
|
+
saved_view_id="00000000-0000-4000-8000-000000000000", # valid UUID but does not exist
|
|
453
|
+
action="change",
|
|
454
|
+
)
|
|
455
|
+
self.assertQuerysetEqual(qs, VRF.objects.none(), ordered=False)
|
nautobot/core/ui/breadcrumbs.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
import logging
|
|
3
|
+
from operator import attrgetter
|
|
3
4
|
from typing import Any, Callable, Literal, Optional, Protocol, Type, Union
|
|
4
5
|
from urllib.parse import urlencode
|
|
5
6
|
|
|
@@ -7,10 +8,12 @@ from django.db.models import Model
|
|
|
7
8
|
from django.template import Context
|
|
8
9
|
from django.urls import NoReverseMatch, reverse
|
|
9
10
|
|
|
11
|
+
from nautobot.core.models.tree_queries import TreeModel
|
|
10
12
|
from nautobot.core.templatetags import helpers
|
|
11
13
|
from nautobot.core.ui.utils import get_absolute_url, render_component_template
|
|
12
14
|
from nautobot.core.utils import lookup
|
|
13
15
|
from nautobot.core.utils.lookup import get_model_for_view_name, get_model_from_name
|
|
16
|
+
from nautobot.core.views.utils import get_obj_from_context
|
|
14
17
|
|
|
15
18
|
logger = logging.getLogger(__name__)
|
|
16
19
|
|
|
@@ -27,6 +30,32 @@ BreadcrumbItemsType = dict[str, list["BaseBreadcrumbItem"]]
|
|
|
27
30
|
ReverseParams = Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
def context_object_attr(attr_path: str, context_key: str = "object"):
|
|
34
|
+
"""
|
|
35
|
+
Helper function that creates callable to fetch the object from context and then nested attributes.
|
|
36
|
+
|
|
37
|
+
Useful for composing breadcrumbs. For example:
|
|
38
|
+
```
|
|
39
|
+
context = Context({"object": Device.objects.first()})
|
|
40
|
+
breadcrumbs = Breadcrumbs(items={"detail": [
|
|
41
|
+
InstanceBreadcrumbItem(instance=context_object_attr("location.location_type")),
|
|
42
|
+
InstanceBreadcrumbItem(instance=context_object_attr("location")),
|
|
43
|
+
]})
|
|
44
|
+
```
|
|
45
|
+
Will render:
|
|
46
|
+
- url to the device.location.location_type detail page
|
|
47
|
+
- url to the device.location detail page
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
>>> context_object_attr("location.name")
|
|
51
|
+
lambda context: context['object'].location.name
|
|
52
|
+
>>> context_object_attr("location.name", context_key="device")
|
|
53
|
+
lambda context: context['device'].location.name
|
|
54
|
+
|
|
55
|
+
"""
|
|
56
|
+
return lambda context: attrgetter(attr_path)(context[context_key]) if context.get(context_key) else None
|
|
57
|
+
|
|
58
|
+
|
|
30
59
|
@dataclass
|
|
31
60
|
class BaseBreadcrumbItem:
|
|
32
61
|
"""
|
|
@@ -128,17 +157,17 @@ class BaseBreadcrumbItem:
|
|
|
128
157
|
"""
|
|
129
158
|
Construct the (URL, label) pair for the breadcrumb.
|
|
130
159
|
|
|
131
|
-
Combines `get_url()` and `get_label()` and applies
|
|
160
|
+
Combines `get_url()` and `get_label()` and applies formatting to the label.
|
|
132
161
|
|
|
133
162
|
Args:
|
|
134
163
|
context (Context): Context object used to resolve the breadcrumb parts.
|
|
135
164
|
|
|
136
165
|
Returns:
|
|
137
166
|
tuple[str, Optional[str]]: A tuple of (URL, label), where URL may be an empty string
|
|
138
|
-
if unresolved, and label
|
|
167
|
+
if unresolved, and label.
|
|
139
168
|
"""
|
|
140
169
|
url = self.get_url(context) or ""
|
|
141
|
-
label =
|
|
170
|
+
label = self.get_label(context)
|
|
142
171
|
return url, label
|
|
143
172
|
|
|
144
173
|
|
|
@@ -192,9 +221,14 @@ class ViewNameBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
192
221
|
|
|
193
222
|
def get_label(self, context: Context) -> str:
|
|
194
223
|
if self.label_from_view_name:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
224
|
+
try:
|
|
225
|
+
model = get_model_for_view_name(self.get_view_name(context))
|
|
226
|
+
if model is not None:
|
|
227
|
+
return helpers.bettertitle(model._meta.verbose_name_plural)
|
|
228
|
+
except ValueError:
|
|
229
|
+
# `get_model_for_view_name` is not working properly with some proper paths like "home"
|
|
230
|
+
# and because by default we're trying to resolve label by using `list_url` this error may occur in some apps
|
|
231
|
+
pass
|
|
198
232
|
return super().get_label(context)
|
|
199
233
|
|
|
200
234
|
def get_view_name(self, context: Context) -> Optional[str]:
|
|
@@ -217,7 +251,7 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
217
251
|
|
|
218
252
|
Attributes:
|
|
219
253
|
model (Union[str, Type[Model], None, Callable[[Context], Union[str, Type[Model], None]]): Django model class, instance, or dotted path string or callable that returns one of this.
|
|
220
|
-
model_key (Optional[str]): Context key to fetch a model class, instance or dotted path string.
|
|
254
|
+
model_key (Optional[str]): Context key to fetch a model class, instance or dotted path string. By default: "object".
|
|
221
255
|
action (str): Action to use when resolving a model-based route (default: "list").
|
|
222
256
|
label_type (Literal["singular", "plural"]): Whether to use `verbose_name` or `verbose_name_plural`.
|
|
223
257
|
reverse_kwargs (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments passed to `reverse()`.
|
|
@@ -236,7 +270,7 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
236
270
|
"""
|
|
237
271
|
|
|
238
272
|
model: Union[ModelType, Callable[[Context], ModelType]] = None
|
|
239
|
-
model_key: Optional[str] =
|
|
273
|
+
model_key: Optional[str] = "object"
|
|
240
274
|
action: str = "list"
|
|
241
275
|
label_type: ModelLabelType = "plural"
|
|
242
276
|
reverse_kwargs: ReverseParams = None
|
|
@@ -276,21 +310,26 @@ class ModelBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
276
310
|
|
|
277
311
|
model_obj = self.get_model(context)
|
|
278
312
|
name_attr = "verbose_name" if self.label_type == "singular" else "verbose_name_plural"
|
|
313
|
+
label = ""
|
|
279
314
|
|
|
280
315
|
if model_obj is not None:
|
|
281
316
|
if isinstance(model_obj, str):
|
|
282
317
|
model_cls = get_model_from_name(model_obj)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
318
|
+
label = getattr(model_cls._meta, name_attr)
|
|
319
|
+
else:
|
|
320
|
+
label = getattr(model_obj._meta, name_attr)
|
|
321
|
+
|
|
322
|
+
return helpers.bettertitle(label)
|
|
286
323
|
|
|
287
324
|
def get_model(self, context: Context) -> ModelType:
|
|
288
|
-
if self.model_key:
|
|
289
|
-
return context.get(self.model_key)
|
|
290
325
|
if self.model:
|
|
291
326
|
if callable(self.model):
|
|
292
327
|
return self.model(context)
|
|
293
328
|
return self.model
|
|
329
|
+
|
|
330
|
+
if self.model_key:
|
|
331
|
+
return context.get(self.model_key)
|
|
332
|
+
|
|
294
333
|
return None
|
|
295
334
|
|
|
296
335
|
|
|
@@ -303,8 +342,8 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
303
342
|
Label will be generated from object, but you can still override it.
|
|
304
343
|
|
|
305
344
|
Attributes:
|
|
306
|
-
instance_key (Optional[str]): Context key to fetch a Django model instance for building the breadcrumb.
|
|
307
|
-
instance (Callable[[Context], Optional[Model]): Callable to fetch the instance from context. If
|
|
345
|
+
instance_key (Optional[str]): Context key to fetch a Django model instance for building the breadcrumb. Default: "object".
|
|
346
|
+
instance (Union[Callable[[Context], Optional[Model]], Model, None]): Callable to fetch the instance from context. If
|
|
308
347
|
should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
|
|
309
348
|
label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
|
|
310
349
|
label_key (Optional[str]): Optional key to take label from the context.
|
|
@@ -316,8 +355,8 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
316
355
|
("/dcim/devices/1234", "Custom Device Label") # Assuming that under "object" there is a Device instance
|
|
317
356
|
"""
|
|
318
357
|
|
|
319
|
-
instance_key: str = "object"
|
|
320
|
-
instance:
|
|
358
|
+
instance_key: Optional[str] = "object"
|
|
359
|
+
instance: Union[Callable[[Context], Optional[Model]], Model, None] = None
|
|
321
360
|
label: Union[Callable[[Context], str], WithStr, None] = None
|
|
322
361
|
|
|
323
362
|
def get_url(self, context: Context) -> Optional[str]:
|
|
@@ -361,9 +400,95 @@ class InstanceBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
361
400
|
Optional[Model]: Instance from context.
|
|
362
401
|
"""
|
|
363
402
|
if self.instance:
|
|
364
|
-
|
|
403
|
+
if callable(self.instance):
|
|
404
|
+
return self.instance(context)
|
|
405
|
+
return self.instance
|
|
406
|
+
|
|
407
|
+
if self.instance_key:
|
|
408
|
+
return context.get(self.instance_key)
|
|
409
|
+
|
|
410
|
+
return None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@dataclass
|
|
414
|
+
class InstanceParentBreadcrumbItem(InstanceBreadcrumbItem):
|
|
415
|
+
"""
|
|
416
|
+
Breadcrumb item prepared to be "parent" or "group" of given model.
|
|
417
|
+
|
|
418
|
+
It will create a breadcrumb item to the object list but will try to filter it by given parent.
|
|
419
|
+
|
|
420
|
+
Attributes:
|
|
421
|
+
parent_key (str): Instance attribute to get the parent instance. Default: "parent".
|
|
422
|
+
parent_query_param (Optional[str]): Query param name under which parent lookup key will be added into breadcrumb url. If None, will be the same as `parent_key`.
|
|
423
|
+
parent_lookup_key (Optional[str]): Parent attribute which will be used to build the url and filter the instance list url. Can be set to None to use parent as key.
|
|
424
|
+
|
|
425
|
+
Examples:
|
|
426
|
+
>>> InstanceParentBreadcrumbItem()
|
|
427
|
+
("/dcim/devices?parent=<parent.pk>", "Parent") # Assuming that under "object" there is a Device instance and has "parent"
|
|
428
|
+
>>> InstanceParentBreadcrumbItem(parent_key="group", parent_lookup_key="name")
|
|
429
|
+
("/dcim/devices?group=<group_name>", "Group") # Assuming that under "object" there is a Device instance and has "group"
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
parent_key: str = "parent"
|
|
433
|
+
parent_query_param: Optional[str] = None
|
|
434
|
+
parent_lookup_key: Optional[str] = "pk"
|
|
435
|
+
|
|
436
|
+
def __post_init__(self):
|
|
437
|
+
"""
|
|
438
|
+
Set `parent_query_param` if not filled.
|
|
439
|
+
"""
|
|
440
|
+
if self.parent_query_param is None:
|
|
441
|
+
self.parent_query_param = self.parent_key
|
|
442
|
+
|
|
443
|
+
def get_url(self, context: Context) -> Optional[str]:
|
|
444
|
+
"""
|
|
445
|
+
Resolve the URL for the breadcrumb item based on the instance and provided parent.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
context (Context): The current template context.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Optional[str]: The URL as a string, or None.
|
|
452
|
+
"""
|
|
453
|
+
instance = self.get_instance(context)
|
|
454
|
+
parent = self.get_parent_attr(instance)
|
|
455
|
+
if not instance or not parent:
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
view_name = lookup.get_route_for_model(instance, "list")
|
|
459
|
+
return self.reverse_view_name(view_name, context, reverse_query_params=self.get_reverse_query_params(parent))
|
|
460
|
+
|
|
461
|
+
def get_label(self, context: Context) -> str:
|
|
462
|
+
"""
|
|
463
|
+
Get the label (display text) for the breadcrumb from instance and provided parent.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
context (Context): The current template context.
|
|
365
467
|
|
|
366
|
-
|
|
468
|
+
Returns:
|
|
469
|
+
str: Label as a string.
|
|
470
|
+
"""
|
|
471
|
+
if self.label or self.label_key:
|
|
472
|
+
return super().get_label(context)
|
|
473
|
+
instance = self.get_instance(context)
|
|
474
|
+
parent = self.get_parent_attr(instance)
|
|
475
|
+
if not instance or not parent:
|
|
476
|
+
return ""
|
|
477
|
+
return getattr(parent, "display", str(parent))
|
|
478
|
+
|
|
479
|
+
def get_reverse_query_params(self, parent: Model) -> Optional[dict]:
|
|
480
|
+
if self.parent_lookup_key is None:
|
|
481
|
+
return {self.parent_query_param: parent}
|
|
482
|
+
|
|
483
|
+
if hasattr(parent, self.parent_lookup_key):
|
|
484
|
+
if query_param := getattr(parent, self.parent_lookup_key):
|
|
485
|
+
return {self.parent_query_param: query_param}
|
|
486
|
+
return {}
|
|
487
|
+
|
|
488
|
+
def get_parent_attr(self, instance: Model) -> Optional[Model]:
|
|
489
|
+
if hasattr(instance, self.parent_key):
|
|
490
|
+
return getattr(instance, self.parent_key) or None
|
|
491
|
+
return None
|
|
367
492
|
|
|
368
493
|
|
|
369
494
|
class Breadcrumbs:
|
|
@@ -401,6 +526,7 @@ class Breadcrumbs:
|
|
|
401
526
|
# Default breadcrumb if view defines `list_url` in the Context
|
|
402
527
|
ViewNameBreadcrumbItem(
|
|
403
528
|
view_name_key="list_url",
|
|
529
|
+
label_key="title",
|
|
404
530
|
label_from_view_name=True,
|
|
405
531
|
should_render=lambda context: context.get("list_url") is not None,
|
|
406
532
|
),
|
|
@@ -411,7 +537,8 @@ class Breadcrumbs:
|
|
|
411
537
|
def __init__(
|
|
412
538
|
self,
|
|
413
539
|
items: BreadcrumbItemsType = None,
|
|
414
|
-
template: str = "
|
|
540
|
+
template: str = "components/breadcrumbs.html",
|
|
541
|
+
detail_item_label: LabelType = None,
|
|
415
542
|
):
|
|
416
543
|
"""
|
|
417
544
|
Initialize the Breadcrumbs configuration.
|
|
@@ -419,8 +546,10 @@ class Breadcrumbs:
|
|
|
419
546
|
Args:
|
|
420
547
|
items (Optional[dict[str, list[BreadcrumbItem]]]): Default breadcrumb items for each action.
|
|
421
548
|
template (str): The template used to render the breadcrumbs.
|
|
549
|
+
detail_item_label (Union[Callable[[Context], str], WithStr, None]): Custom label for last built-in breadcrumb item with link to the object details.
|
|
422
550
|
"""
|
|
423
551
|
self.template = template
|
|
552
|
+
self.detail_item_label = detail_item_label
|
|
424
553
|
|
|
425
554
|
# Set the default breadcrumbs
|
|
426
555
|
self.items = {
|
|
@@ -433,7 +562,7 @@ class Breadcrumbs:
|
|
|
433
562
|
self.items = {**self.items, **items}
|
|
434
563
|
|
|
435
564
|
# Built-in feature: always add the instance details at the end of breadcrumbs path
|
|
436
|
-
self.items["detail"].append(InstanceBreadcrumbItem())
|
|
565
|
+
self.items["detail"].append(InstanceBreadcrumbItem(label=detail_item_label))
|
|
437
566
|
|
|
438
567
|
def get_breadcrumbs_items(self, context: Context) -> list[tuple[str, str]]:
|
|
439
568
|
"""
|
|
@@ -447,9 +576,9 @@ class Breadcrumbs:
|
|
|
447
576
|
Returns:
|
|
448
577
|
(list[tuple[str, str]]): A list of (url, label) tuples representing breadcrumb entries.
|
|
449
578
|
"""
|
|
450
|
-
action = context.get("view_action", "
|
|
579
|
+
action = context.get("view_action", "")
|
|
451
580
|
detail = context.get("detail", False)
|
|
452
|
-
items = self.get_items_for_action(self.items, action, detail)
|
|
581
|
+
items = self.get_items_for_action(self.items, action, detail, context)
|
|
453
582
|
return [item.as_pair(context) for item in items if item.should_render(context)]
|
|
454
583
|
|
|
455
584
|
def filter_breadcrumbs_items(self, items: list[tuple[str, str]], context: Context) -> list[tuple[str, str]]:
|
|
@@ -457,7 +586,7 @@ class Breadcrumbs:
|
|
|
457
586
|
Filters out all items that both label and url are None or empty str.
|
|
458
587
|
|
|
459
588
|
Args:
|
|
460
|
-
items (list[tuple[str, str]]): breadcrumb items
|
|
589
|
+
items (list[tuple[str, str]]): breadcrumb items pairs.
|
|
461
590
|
context (Context): The view or template context.
|
|
462
591
|
|
|
463
592
|
Returns:
|
|
@@ -478,16 +607,17 @@ class Breadcrumbs:
|
|
|
478
607
|
"""
|
|
479
608
|
return label and label.strip()
|
|
480
609
|
|
|
481
|
-
|
|
482
|
-
|
|
610
|
+
def get_items_for_action(
|
|
611
|
+
self, items: BreadcrumbItemsType, action: str, detail: bool, context: Context
|
|
612
|
+
) -> list[BaseBreadcrumbItem]:
|
|
483
613
|
"""
|
|
484
|
-
Get the breadcrumb items for a specific action,
|
|
485
|
-
and to asterisk (*) if present.
|
|
614
|
+
Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
|
|
486
615
|
|
|
487
616
|
Args:
|
|
488
617
|
items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
|
|
489
618
|
action (str): Current action name (e.g., "list", "detail").
|
|
490
619
|
detail (bool): Whether this is a detail view (for fallback).
|
|
620
|
+
context (Context): The view or template context.
|
|
491
621
|
|
|
492
622
|
Returns:
|
|
493
623
|
list[BaseBreadcrumbItem]: List of breadcrumb items for the action.
|
|
@@ -536,3 +666,65 @@ class Breadcrumbs:
|
|
|
536
666
|
(dict): A dictionary of extra context variables.
|
|
537
667
|
"""
|
|
538
668
|
return {}
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
class AncestorsBreadcrumbs(Breadcrumbs):
|
|
672
|
+
"""
|
|
673
|
+
Breadcrumbs class which can render list of ancestors of given instance.
|
|
674
|
+
|
|
675
|
+
Default behavior:
|
|
676
|
+
- render breadcrumb item with link to the list view
|
|
677
|
+
- dynamically add list of `InstanceBreadcrumbItem` from `ancestors()`
|
|
678
|
+
- adds standard breadcrumb item with link to the details
|
|
679
|
+
"""
|
|
680
|
+
|
|
681
|
+
def get_items_for_action(
|
|
682
|
+
self, items: BreadcrumbItemsType, action: str, detail: bool, context: Context
|
|
683
|
+
) -> list[BaseBreadcrumbItem]:
|
|
684
|
+
"""
|
|
685
|
+
Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
|
|
686
|
+
|
|
687
|
+
For detail action it calls `self.get_detail_items()` method. For other actions, it calls parent class method.
|
|
688
|
+
|
|
689
|
+
Args:
|
|
690
|
+
items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
|
|
691
|
+
action (str): Current action name (e.g., "list", "detail").
|
|
692
|
+
detail (bool): Whether this is a detail view (for fallback).
|
|
693
|
+
context (Context): The view or template context.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
list[BaseBreadcrumbItem]: List of breadcrumb items for the action.
|
|
697
|
+
"""
|
|
698
|
+
if not detail:
|
|
699
|
+
return super().get_items_for_action(items, action, detail, context)
|
|
700
|
+
|
|
701
|
+
return self.get_detail_items(context)
|
|
702
|
+
|
|
703
|
+
def get_detail_items(self, context: Context) -> list[BaseBreadcrumbItem]:
|
|
704
|
+
"""
|
|
705
|
+
Build breadcrumb items for a detail view, including the model, its ancestors, and the instance itself.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
context (Context): Template or view context containing the current object.
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
list[BaseBreadcrumbItem]: Ordered breadcrumb items for the given instance.
|
|
712
|
+
"""
|
|
713
|
+
instance = get_obj_from_context(context)
|
|
714
|
+
return [
|
|
715
|
+
ModelBreadcrumbItem(model=instance),
|
|
716
|
+
*self.get_ancestors_items(instance),
|
|
717
|
+
InstanceBreadcrumbItem(instance=instance, label=self.detail_item_label),
|
|
718
|
+
]
|
|
719
|
+
|
|
720
|
+
def get_ancestors_items(self, instance: TreeModel) -> list[BaseBreadcrumbItem]:
|
|
721
|
+
"""
|
|
722
|
+
Create breadcrumb items for all ancestor objects of the given instance.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
instance (TreeModel): Object from context.
|
|
726
|
+
|
|
727
|
+
Returns:
|
|
728
|
+
list[BaseBreadcrumbItem]: Breadcrumb items for each ancestor of the instance.
|
|
729
|
+
"""
|
|
730
|
+
return [InstanceBreadcrumbItem(instance=ancestor, label=ancestor.name) for ancestor in instance.ancestors()]
|