nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/choices.py +4 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +13 -1
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/cloud/filters.py +1 -1
- nautobot/cloud/navigation.py +3 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +6 -1
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +12 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
- nautobot/core/cli/migrate_deprecated_templates.py +227 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +62 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +35 -19
- nautobot/core/settings.yaml +17 -33
- nautobot/core/signals.py +12 -1
- nautobot/core/tables.py +13 -6
- nautobot/core/templates/40x.html +1 -1
- nautobot/core/templates/500.html +2 -2
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/buttons/export.html +1 -1
- nautobot/core/templates/components/button/dropdown.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +6 -7
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +22 -9
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +6 -5
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_bulk_remove.html +2 -2
- nautobot/core/templates/generic/object_bulk_update.html +5 -4
- nautobot/core/templates/generic/object_create.html +5 -4
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/generic/object_import.html +2 -1
- nautobot/core/templates/generic/object_list.html +12 -4
- nautobot/core/templates/generic/object_notes.html +5 -3
- nautobot/core/templates/generic/object_retrieve.html +4 -5
- nautobot/core/templates/graphene/graphiql.html +7 -8
- nautobot/core/templates/home.html +1 -1
- nautobot/core/templates/import_success.html +2 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/created_updated.html +7 -3
- nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +11 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +3 -9
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -11
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +17 -3
- nautobot/core/templates/system_jobs/import_objects.html +1 -2
- nautobot/core/templates/utilities/confirmation_form.html +2 -2
- nautobot/core/templates/utilities/obj_table.html +10 -2
- nautobot/core/templates/utilities/render_field.html +7 -7
- nautobot/core/templates/utilities/render_jinja2.html +2 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
- nautobot/core/templates/utilities/theme_preview.html +19 -3
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +76 -18
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +41 -17
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -13
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +8 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +27 -28
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_forms.py +55 -13
- nautobot/core/tests/test_jobs.py +144 -3
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +76 -1
- nautobot/core/tests/test_patch_social_django.py +42 -0
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +62 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +123 -4
- nautobot/core/tests/test_utils.py +72 -5
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +70 -29
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +143 -27
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +143 -55
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +20 -9
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +28 -18
- nautobot/core/utils/lookup.py +49 -8
- nautobot/core/utils/module_loading.py +21 -0
- nautobot/core/utils/patch_social_django.py +128 -0
- nautobot/core/views/__init__.py +38 -1
- nautobot/core/views/generic.py +3 -3
- nautobot/core/views/mixins.py +45 -22
- nautobot/core/views/renderers.py +4 -3
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/apps.py +1 -5
- nautobot/data_validation/custom_validators.py +4 -4
- nautobot/data_validation/filters.py +1 -1
- nautobot/data_validation/forms.py +40 -0
- nautobot/data_validation/migrations/0001_initial.py +0 -7
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
- nautobot/data_validation/models.py +16 -7
- nautobot/data_validation/navigation.py +8 -1
- nautobot/data_validation/tables.py +12 -5
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +2 -2
- nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
- nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
- nautobot/data_validation/tests/test_filters.py +8 -6
- nautobot/data_validation/tests/test_models.py +15 -0
- nautobot/data_validation/tests/test_views.py +190 -32
- nautobot/data_validation/urls.py +2 -5
- nautobot/data_validation/views.py +73 -40
- nautobot/dcim/api/serializers.py +3 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +65 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
- nautobot/dcim/forms.py +90 -4
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +98 -64
- nautobot/dcim/models/devices.py +30 -20
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +18 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +15 -15
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_trace.html +0 -4
- nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
- nautobot/dcim/templates/dcim/consoleport.html +7 -6
- nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_component_add.html +2 -2
- nautobot/dcim/templates/dcim/device_create.html +5 -3
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/device_list.html +73 -10
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/frontport.html +10 -9
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +35 -7
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +6 -5
- nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
- nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
- nautobot/dcim/templates/dcim/rearport.html +8 -7
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
- nautobot/dcim/tests/integration/test_controller.py +4 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +194 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +55 -7
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +328 -4
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +132 -29
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +777 -397
- nautobot/extras/api/views.py +60 -45
- nautobot/extras/choices.py +2 -13
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
- nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
- nautobot/extras/{filters/__init__.py → filters.py} +33 -48
- nautobot/extras/forms/forms.py +14 -15
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +11 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
- nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
- nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
- nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +33 -14
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +60 -4
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +23 -2
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/navigation.py +13 -9
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -9
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +64 -68
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/approval_dashboard.html +7 -5
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
- nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
- nautobot/extras/templates/extras/inc/json_format.html +10 -3
- nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
- nautobot/extras/templates/extras/job.html +29 -12
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
- nautobot/extras/templates/extras/job_edit.html +52 -46
- nautobot/extras/templates/extras/job_list.html +29 -25
- nautobot/extras/templates/extras/marketplace.html +5 -9
- nautobot/extras/templates/extras/object_configcontext.html +1 -1
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
- nautobot/extras/templates/extras/plugin_detail.html +29 -24
- nautobot/extras/templates/extras/plugins_list.html +16 -26
- nautobot/extras/templates/extras/role_retrieve.html +64 -0
- nautobot/extras/templates/extras/scheduledjob.html +4 -2
- nautobot/extras/templates/extras/secret_create.html +1 -1
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +9 -10
- nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +90 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +59 -5
- nautobot/extras/tests/test_customfields.py +22 -13
- nautobot/extras/tests/test_customfields_filters.py +479 -0
- nautobot/extras/tests/test_dynamicgroups.py +39 -1
- nautobot/extras/tests/test_filters.py +57 -22
- nautobot/extras/tests/test_forms.py +18 -21
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +1 -0
- nautobot/extras/tests/test_models.py +51 -33
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +52 -112
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +164 -71
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +53 -38
- nautobot/ipam/formfields.py +1 -1
- nautobot/ipam/forms.py +6 -3
- nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
- nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
- nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/navigation.py +3 -2
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/tables.py +19 -6
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
- nautobot/ipam/templates/ipam/ipaddress.html +14 -0
- nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +13 -6
- nautobot/ipam/tests/test_filters.py +36 -1
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +44 -2
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +13 -14
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +53 -11
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +1 -1
- nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
- nautobot/project-static/nautobot-icons/elements.svg +1 -1
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +1 -1
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +1 -1
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +1 -1
- nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
- nautobot/project-static/nautobot-icons/star.svg +1 -1
- nautobot/tenancy/api/serializers.py +1 -0
- nautobot/tenancy/api/views.py +2 -1
- nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
- nautobot/tenancy/navigation.py +3 -1
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +112 -30
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/users/templates/users/profile.html +45 -12
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/views.py +4 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/navigation.py +3 -2
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +45 -4
- nautobot/virtualization/views.py +4 -1
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +219 -0
- nautobot/vpn/filters.py +234 -0
- nautobot/vpn/forms.py +487 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +535 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +383 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +336 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +293 -0
- nautobot/vpn/tests/test_models.py +147 -0
- nautobot/vpn/tests/test_views.py +300 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +495 -0
- nautobot/wireless/navigation.py +3 -2
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/template_content.py +0 -42
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
nautobot/core/utils/lookup.py
CHANGED
|
@@ -11,9 +11,10 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
11
11
|
from django.core.exceptions import ObjectDoesNotExist
|
|
12
12
|
from django.db.models import ForeignKey, Model
|
|
13
13
|
from django.urls import get_resolver, resolve, reverse, URLPattern, URLResolver
|
|
14
|
-
from django.utils.module_loading import import_string
|
|
15
14
|
from django.views.generic.base import RedirectView
|
|
16
15
|
|
|
16
|
+
from nautobot.core.utils.module_loading import import_string_optional
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
def resolve_attr(obj, dotted_field):
|
|
19
20
|
"""
|
|
@@ -60,6 +61,14 @@ def resolve_attr(obj, dotted_field):
|
|
|
60
61
|
return str(val) if val else None
|
|
61
62
|
|
|
62
63
|
|
|
64
|
+
def get_breadcrumbs_for_model(model, view_type: str = "List"):
|
|
65
|
+
"""Get a UI Component Framework 'Breadcrumbs' instance for the given model's related UIViewSet or generic view."""
|
|
66
|
+
view = get_view_for_model(model)
|
|
67
|
+
if hasattr(view, "get_breadcrumbs"):
|
|
68
|
+
return view.get_breadcrumbs(model, view_type=view_type)
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
63
72
|
def get_changes_for_model(model):
|
|
64
73
|
"""
|
|
65
74
|
Return a queryset of ObjectChanges for a model or instance. The queryset will be filtered
|
|
@@ -77,6 +86,30 @@ def get_changes_for_model(model):
|
|
|
77
86
|
raise TypeError(f"{model!r} is not a Django Model class or instance")
|
|
78
87
|
|
|
79
88
|
|
|
89
|
+
def get_detail_view_components_context_for_model(model) -> dict:
|
|
90
|
+
"""Helper method for DistinctViewTabs etc. to retrieve the UI Component Framework context for the base detail view.
|
|
91
|
+
|
|
92
|
+
Functionally equivalent to calling `get_breadcrumbs_for_model()`, `get_object_detail_content_for_model()`, and
|
|
93
|
+
`get_view_titles_for_model()`, but marginally more efficient.
|
|
94
|
+
"""
|
|
95
|
+
context = {
|
|
96
|
+
"breadcrumbs": None,
|
|
97
|
+
"object_detail_content": None,
|
|
98
|
+
"view_titles": None,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
view = get_view_for_model(model, view_type="")
|
|
102
|
+
if view is not None:
|
|
103
|
+
if hasattr(view, "get_breadcrumbs"):
|
|
104
|
+
context["breadcrumbs"] = view.get_breadcrumbs(model, view_type="")
|
|
105
|
+
if hasattr(view, "get_view_titles"):
|
|
106
|
+
context["view_titles"] = view.get_view_titles(model, view_type="")
|
|
107
|
+
if hasattr(view, "object_detail_content"):
|
|
108
|
+
context["object_detail_content"] = view.object_detail_content
|
|
109
|
+
|
|
110
|
+
return context
|
|
111
|
+
|
|
112
|
+
|
|
80
113
|
def get_model_from_name(model_name):
|
|
81
114
|
"""Given a full model name in dotted format (example: `dcim.model`), a model class is returned if valid.
|
|
82
115
|
|
|
@@ -181,13 +214,7 @@ def get_related_class_for_model(model, module_name, object_suffix):
|
|
|
181
214
|
object_name = f"{model.__name__}{object_suffix}"
|
|
182
215
|
object_path = f"{app_config.name}.{module_name}.{object_name}"
|
|
183
216
|
|
|
184
|
-
|
|
185
|
-
return import_string(object_path)
|
|
186
|
-
# The name of the module is not correct or unable to find the desired object for this model
|
|
187
|
-
except (AttributeError, ImportError, ModuleNotFoundError):
|
|
188
|
-
pass
|
|
189
|
-
|
|
190
|
-
return None
|
|
217
|
+
return import_string_optional(object_path)
|
|
191
218
|
|
|
192
219
|
|
|
193
220
|
def get_filterset_for_model(model):
|
|
@@ -227,6 +254,12 @@ def get_form_for_model(model, form_prefix=""):
|
|
|
227
254
|
return get_related_class_for_model(model, module_name="forms", object_suffix=object_suffix)
|
|
228
255
|
|
|
229
256
|
|
|
257
|
+
def get_object_detail_content_for_model(model):
|
|
258
|
+
"""Get the UI Component Framework 'object_detail_content' for the given model's related UIViewSet or ObjectView."""
|
|
259
|
+
view = get_view_for_model(model)
|
|
260
|
+
return getattr(view, "object_detail_content", None)
|
|
261
|
+
|
|
262
|
+
|
|
230
263
|
def get_related_field_for_models(from_model, to_model):
|
|
231
264
|
"""
|
|
232
265
|
Find the field on `from_model` that is a relation to `to_model`.
|
|
@@ -294,6 +327,14 @@ def get_view_for_model(model, view_type=""):
|
|
|
294
327
|
return result
|
|
295
328
|
|
|
296
329
|
|
|
330
|
+
def get_view_titles_for_model(model, view_type: str = "List"):
|
|
331
|
+
"""Get a UI Component Framework 'Titles' instance for the given model's related UIViewSet or generic view."""
|
|
332
|
+
view = get_view_for_model(model)
|
|
333
|
+
if hasattr(view, "get_view_titles"):
|
|
334
|
+
return view.get_view_titles(model, view_type=view_type)
|
|
335
|
+
return None
|
|
336
|
+
|
|
337
|
+
|
|
297
338
|
def get_model_for_view_name(view_name):
|
|
298
339
|
"""
|
|
299
340
|
Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
|
|
@@ -7,9 +7,30 @@ import os
|
|
|
7
7
|
import pkgutil
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
+
from django.utils.module_loading import import_string
|
|
11
|
+
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def import_string_optional(dotted_path):
|
|
16
|
+
"""An extension/wrapper of Django's `import_string()` that returns `None` if no such dotted path exists."""
|
|
17
|
+
try:
|
|
18
|
+
return import_string(dotted_path)
|
|
19
|
+
except ModuleNotFoundError as err:
|
|
20
|
+
# No such module
|
|
21
|
+
module_name, _ = dotted_path.rsplit(".", 1)
|
|
22
|
+
if module_name.startswith(err.name): # tried to import foo.bar.baz but couldn't find foo.bar, etc.
|
|
23
|
+
return None
|
|
24
|
+
# Some import *from within* the given module couldn't find what it was looking for?
|
|
25
|
+
raise
|
|
26
|
+
except ImportError as err:
|
|
27
|
+
if "does not define" in str(err):
|
|
28
|
+
# Exception raised by Django if the module exists but has no such attribute
|
|
29
|
+
return None
|
|
30
|
+
# Maybe a legitimate problem with the import?
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
|
|
13
34
|
@contextmanager
|
|
14
35
|
def _temporarily_add_to_sys_path(path):
|
|
15
36
|
"""
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
2
|
+
from django.db import router, transaction
|
|
3
|
+
from django.db.utils import IntegrityError
|
|
4
|
+
from social_core.exceptions import AuthAlreadyAssociated
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Social Auth Account Takeover Vulnerability Patch
|
|
8
|
+
=================================================
|
|
9
|
+
|
|
10
|
+
This module patches CVE-2025-61783, a medium security vulnerability in social_django that allows
|
|
11
|
+
account takeover when using OAuth providers that don't verify email addresses.
|
|
12
|
+
|
|
13
|
+
VULNERABILITY OVERVIEW
|
|
14
|
+
----------------------
|
|
15
|
+
The vulnerability exists in social_django.storage.DjangoUserMixin.create_user(),
|
|
16
|
+
specifically in how it handles IntegrityError exceptions. When user creation fails due to
|
|
17
|
+
a duplicate email or username, the original code catches the IntegrityError and blindly
|
|
18
|
+
retrieves an existing user via manager.get(), returning that user without verifying that
|
|
19
|
+
a social auth association exists for the provider/UID combination.
|
|
20
|
+
|
|
21
|
+
PATCHING STRATEGY
|
|
22
|
+
-----------------
|
|
23
|
+
This implementation patches the `create_user` method on the `user` class property of
|
|
24
|
+
DjangoStorage, which is where the vulnerability manifests. The patch changes the behavior
|
|
25
|
+
to raise AuthAlreadyAssociated when an IntegrityError occurs, preventing the silent
|
|
26
|
+
return of an existing user.
|
|
27
|
+
|
|
28
|
+
By patching at this level, we:
|
|
29
|
+
- Maintain compatibility with custom pipelines
|
|
30
|
+
- Don't require changes to user's social auth configuration
|
|
31
|
+
- Apply the fix exactly where the vulnerability occurs
|
|
32
|
+
- Preserve all other social auth functionality
|
|
33
|
+
|
|
34
|
+
REMOVAL
|
|
35
|
+
-------
|
|
36
|
+
Remove this patch when upgrading to social-auth-app-django >= 5.6.0
|
|
37
|
+
(version that includes PR #803 merged into the main branch).
|
|
38
|
+
|
|
39
|
+
To verify if you still need the patch:
|
|
40
|
+
pip show social-auth-app-django
|
|
41
|
+
# Check version against PR #803 merge status
|
|
42
|
+
|
|
43
|
+
REFERENCES
|
|
44
|
+
----------
|
|
45
|
+
- Vulnerability Report: https://github.com/python-social-auth/social-app-django/security/advisories/GHSA-wv4w-6qv2-qqfg
|
|
46
|
+
- Original Issue: https://github.com/python-social-auth/social-app-django/issues/220
|
|
47
|
+
- Official Fix PR: https://github.com/python-social-auth/social-app-django/pull/803
|
|
48
|
+
|
|
49
|
+
SECURITY NOTICE
|
|
50
|
+
---------------
|
|
51
|
+
This patch addresses a MEDIUM security vulnerability
|
|
52
|
+
|
|
53
|
+
Disabling this patch without mitigation will expose your application to account
|
|
54
|
+
takeover attacks.
|
|
55
|
+
|
|
56
|
+
AUTHOR & MAINTENANCE
|
|
57
|
+
--------------------
|
|
58
|
+
Patch implemented as temporary security measure for Nautobot deployment.
|
|
59
|
+
As picking up the latest social_django would require a major version upgrade of Django,
|
|
60
|
+
which itself would require a breaking change to the Nautobot configuration, this patch
|
|
61
|
+
is intended to be a stopgap until such time as Nautobot can upgrade to a version of
|
|
62
|
+
social_django that includes the fix.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def patch_django_storage(original_django_storage):
|
|
67
|
+
"""
|
|
68
|
+
Apply security patch to DjangoStorage.user.create_user method.
|
|
69
|
+
|
|
70
|
+
This patches the vulnerability in python-social-auth where create_user
|
|
71
|
+
catches IntegrityError and blindly returns an existing user, enabling
|
|
72
|
+
account takeover via unverified OAuth providers.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
storage_class (DjangoStorage): The original DjangoStorage class to patch.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
None
|
|
79
|
+
|
|
80
|
+
Note:
|
|
81
|
+
The patch is a nearly verbatim copy of the original create_user method
|
|
82
|
+
from social_django.storage.DjangoUserMixin from 5.4.3, except that it
|
|
83
|
+
adopts the fail-closed change described in
|
|
84
|
+
https://github.com/python-social-auth/social-app-django/pull/803
|
|
85
|
+
|
|
86
|
+
The modified lines are called out with "Patched logic" comments below.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def patched_create_user(cls, *args, **kwargs):
|
|
90
|
+
username_field = cls.username_field()
|
|
91
|
+
if "username" in kwargs:
|
|
92
|
+
if username_field not in kwargs:
|
|
93
|
+
kwargs[username_field] = kwargs.pop("username")
|
|
94
|
+
else:
|
|
95
|
+
# If username_field is 'email' and there is no field named "username"
|
|
96
|
+
# then latest should be removed from kwargs.
|
|
97
|
+
try:
|
|
98
|
+
cls.user_model()._meta.get_field("username")
|
|
99
|
+
except FieldDoesNotExist:
|
|
100
|
+
kwargs.pop("username")
|
|
101
|
+
try:
|
|
102
|
+
if hasattr(transaction, "atomic"):
|
|
103
|
+
# In Django versions that have an "atomic" transaction decorator / context
|
|
104
|
+
# manager, there's a transaction wrapped around this call.
|
|
105
|
+
# If the create fails below due to an IntegrityError, ensure that the transaction
|
|
106
|
+
# stays undamaged by wrapping the create in an atomic.
|
|
107
|
+
using = router.db_for_write(cls.user_model())
|
|
108
|
+
with transaction.atomic(using=using):
|
|
109
|
+
user = cls.user_model()._default_manager.create_user(*args, **kwargs)
|
|
110
|
+
else:
|
|
111
|
+
user = cls.user_model()._default_manager.create_user(*args, **kwargs)
|
|
112
|
+
except IntegrityError as exc:
|
|
113
|
+
# ORIGINAL CODE BELOW:
|
|
114
|
+
# # If email comes in as None it won't get found in the get
|
|
115
|
+
# if kwargs.get("email", True) is None:
|
|
116
|
+
# kwargs["email"] = ""
|
|
117
|
+
# try:
|
|
118
|
+
# user = cls.user_model()._default_manager.get(*args, **kwargs)
|
|
119
|
+
# except cls.user_model().DoesNotExist:
|
|
120
|
+
# raise exc
|
|
121
|
+
|
|
122
|
+
# BEGIN Patched logic
|
|
123
|
+
raise AuthAlreadyAssociated(None) from exc
|
|
124
|
+
# END Patched logic
|
|
125
|
+
return user
|
|
126
|
+
|
|
127
|
+
# Apply the patch to the original DjangoStorage.user.create_user method
|
|
128
|
+
original_django_storage.user.create_user = classmethod(patched_create_user)
|
nautobot/core/views/__init__.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import datetime
|
|
3
|
+
from importlib import resources
|
|
3
4
|
import logging
|
|
5
|
+
import mimetypes
|
|
4
6
|
import os
|
|
5
7
|
import platform
|
|
6
8
|
import posixpath
|
|
@@ -16,7 +18,7 @@ from django.contrib.auth.decorators import permission_required
|
|
|
16
18
|
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
|
|
17
19
|
from django.contrib.contenttypes.models import ContentType
|
|
18
20
|
from django.core.cache import cache
|
|
19
|
-
from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
21
|
+
from django.http import FileResponse, HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
20
22
|
from django.shortcuts import get_object_or_404, render
|
|
21
23
|
from django.template import loader, RequestContext, Template
|
|
22
24
|
from django.template.exceptions import TemplateDoesNotExist
|
|
@@ -61,6 +63,7 @@ from nautobot.core.views.utils import (
|
|
|
61
63
|
)
|
|
62
64
|
from nautobot.extras.forms import GraphQLQueryForm
|
|
63
65
|
from nautobot.extras.models import FileProxy, GraphQLQuery, Status
|
|
66
|
+
from nautobot.extras.plugins.urls import BASE_URL_TO_APP_LABEL
|
|
64
67
|
from nautobot.extras.registry import registry
|
|
65
68
|
from nautobot.extras.tables import StatusTable
|
|
66
69
|
|
|
@@ -144,6 +147,40 @@ class HomeView(AccessMixin, TemplateView):
|
|
|
144
147
|
return self.render_to_response(context)
|
|
145
148
|
|
|
146
149
|
|
|
150
|
+
class AppDocsView(LoginRequiredMixin, View):
|
|
151
|
+
"""
|
|
152
|
+
Serve documentation files for any pip-installed app from inside the package,
|
|
153
|
+
only for authenticated users.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def get(self, request, app_base_url, path="index.html"):
|
|
157
|
+
app_label = BASE_URL_TO_APP_LABEL.get(app_base_url)
|
|
158
|
+
if not app_label:
|
|
159
|
+
return JsonResponse({"detail": f"Unknown base_url '{app_base_url}'."}, status=404)
|
|
160
|
+
try:
|
|
161
|
+
base_dir = resources.files(app_label)
|
|
162
|
+
except ModuleNotFoundError:
|
|
163
|
+
return JsonResponse({"detail": f"App {app_label} not found."}, status=404)
|
|
164
|
+
|
|
165
|
+
# Dir to documentation inside the package
|
|
166
|
+
docs_dir = base_dir / "docs"
|
|
167
|
+
# Normalize path to avoid (../) etc.
|
|
168
|
+
normalized_path = posixpath.normpath(path).lstrip("/")
|
|
169
|
+
file_path = docs_dir / normalized_path
|
|
170
|
+
|
|
171
|
+
# Additional check to ensure the resolved path is still within docs_dir
|
|
172
|
+
if not file_path.resolve().is_relative_to(docs_dir.resolve()):
|
|
173
|
+
return JsonResponse({"detail": "Access denied."}, status=403)
|
|
174
|
+
|
|
175
|
+
if not file_path.is_file():
|
|
176
|
+
return JsonResponse({"detail": f"File {file_path} not found."}, status=404)
|
|
177
|
+
|
|
178
|
+
# Determine the MIME type based on the file extension and return the file as an HTTP response.
|
|
179
|
+
# This ensures that browsers interpret the file correctly (e.g., HTML, CSS, JS, images).
|
|
180
|
+
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
181
|
+
return FileResponse(open(file_path, "rb"), content_type=content_type)
|
|
182
|
+
|
|
183
|
+
|
|
147
184
|
class MediaView(AccessMixin, View):
|
|
148
185
|
"""
|
|
149
186
|
Serves media files while enforcing login restrictions.
|
nautobot/core/views/generic.py
CHANGED
|
@@ -587,7 +587,7 @@ class ObjectDeleteView(UIComponentsMixin, GetReturnURLMixin, ObjectPermissionReq
|
|
|
587
587
|
"""
|
|
588
588
|
|
|
589
589
|
queryset: Optional[QuerySet] = None # TODO: required, declared Optional only to avoid a breaking change
|
|
590
|
-
template_name = "generic/
|
|
590
|
+
template_name = "generic/object_destroy.html"
|
|
591
591
|
|
|
592
592
|
def get_required_permission(self):
|
|
593
593
|
return get_permission_for_model(self.queryset.model, "delete")
|
|
@@ -1044,7 +1044,7 @@ class BulkEditView(
|
|
|
1044
1044
|
filterset: Optional[type[FilterSet]] = None
|
|
1045
1045
|
table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
|
|
1046
1046
|
form: Optional[type[Form]] = None # TODO: required, declared Optional only to avoid a breaking change
|
|
1047
|
-
template_name = "generic/
|
|
1047
|
+
template_name = "generic/object_bulk_update.html"
|
|
1048
1048
|
|
|
1049
1049
|
def get_required_permission(self):
|
|
1050
1050
|
return get_permission_for_model(self.queryset.model, "change")
|
|
@@ -1246,7 +1246,7 @@ class BulkDeleteView(
|
|
|
1246
1246
|
filterset: Optional[type[FilterSet]] = None
|
|
1247
1247
|
table: Optional[type[Table]] = None # TODO: required, declared Optional only to avoid a breaking change
|
|
1248
1248
|
form: Optional[type[Form]] = None
|
|
1249
|
-
template_name = "generic/
|
|
1249
|
+
template_name = "generic/object_bulk_destroy.html"
|
|
1250
1250
|
|
|
1251
1251
|
def get_required_permission(self):
|
|
1252
1252
|
return get_permission_for_model(self.queryset.model, "delete")
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -73,6 +73,7 @@ PERMISSIONS_ACTION_MAP = {
|
|
|
73
73
|
"bulk_update": "change",
|
|
74
74
|
"changelog": "view",
|
|
75
75
|
"notes": "view",
|
|
76
|
+
"data_compliance": "view",
|
|
76
77
|
"approve": "change",
|
|
77
78
|
"deny": "change",
|
|
78
79
|
}
|
|
@@ -262,52 +263,57 @@ class UIComponentsMixin:
|
|
|
262
263
|
breadcrumbs: ClassVar[Optional[Breadcrumbs]] = None
|
|
263
264
|
view_titles: ClassVar[Optional[Titles]] = None
|
|
264
265
|
|
|
265
|
-
|
|
266
|
+
@classmethod
|
|
267
|
+
def get_view_titles(cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List") -> Titles:
|
|
266
268
|
"""
|
|
267
269
|
Resolve and return the `Titles` component instance.
|
|
268
270
|
|
|
269
271
|
Resolution order:
|
|
270
|
-
1) If
|
|
271
|
-
2) Else, if `model` is provided, copy the `view_titles` from the view
|
|
272
|
-
|
|
272
|
+
1) If `.view_titles` is set on the current view, use it.
|
|
273
|
+
2) Else, if `model` is provided, copy the `view_titles` from the view class associated with that model
|
|
274
|
+
via `lookup.get_view_for_model(model, action)`.
|
|
273
275
|
3) Else, instantiate and return the default `Titles()`.
|
|
274
276
|
|
|
275
277
|
Args:
|
|
276
278
|
model: A Django model **class**, **instance**, dotted name string, or `None`.
|
|
277
279
|
Passed to `lookup.get_view_for_model()` to find the related view class.
|
|
278
280
|
If `None`, only local/default resolution is used.
|
|
279
|
-
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
281
|
+
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
282
|
+
(e.g., `"List"` or empty to construct `"DeviceView"` string).
|
|
280
283
|
|
|
281
284
|
Returns:
|
|
282
285
|
Titles: A concrete `Titles` component instance ready to use.
|
|
283
286
|
"""
|
|
284
|
-
return
|
|
287
|
+
return cls._resolve_component("view_titles", Titles, model, view_type)
|
|
285
288
|
|
|
289
|
+
@classmethod
|
|
286
290
|
def get_breadcrumbs(
|
|
287
|
-
|
|
291
|
+
cls, model: Union[None, str, Type[Model], Model] = None, view_type: str = "List"
|
|
288
292
|
) -> Breadcrumbs:
|
|
289
293
|
"""
|
|
290
294
|
Resolve and return the `Breadcrumbs` component instance.
|
|
291
295
|
|
|
292
296
|
Resolution order mirrors `get_view_titles()`:
|
|
293
|
-
1) Use
|
|
294
|
-
2) Else, if `model` is provided, copy the `breadcrumbs` from the view
|
|
295
|
-
|
|
297
|
+
1) Use `.breadcrumbs` if set locally.
|
|
298
|
+
2) Else, if `model` is provided, copy the `breadcrumbs` from the view class associated with that model
|
|
299
|
+
via `lookup.get_view_for_model(model, action)`.
|
|
296
300
|
3) Else return a new default `Breadcrumbs()`.
|
|
297
301
|
|
|
298
302
|
Args:
|
|
299
303
|
model: A Django model **class**, **instance**, dotted name string, or `None`.
|
|
300
304
|
Passed to `lookup.get_view_for_model()` to find the related view class.
|
|
301
305
|
If `None`, only local/default resolution is used.
|
|
302
|
-
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
306
|
+
view_type: Logical view type used by `lookup.get_view_for_model()`
|
|
307
|
+
(e.g., `"List"` or empty to construct `"DeviceView"` string).
|
|
303
308
|
|
|
304
309
|
Returns:
|
|
305
310
|
Breadcrumbs: A concrete `Breadcrumbs` component instance.
|
|
306
311
|
"""
|
|
307
|
-
return
|
|
312
|
+
return cls._resolve_component("breadcrumbs", Breadcrumbs, model, view_type)
|
|
308
313
|
|
|
314
|
+
@classmethod
|
|
309
315
|
def _resolve_component(
|
|
310
|
-
|
|
316
|
+
cls,
|
|
311
317
|
attr_name: str,
|
|
312
318
|
default_cls: Type[Union[Breadcrumbs, Titles]],
|
|
313
319
|
model: Union[None, str, Type[Model], Model] = None,
|
|
@@ -328,14 +334,14 @@ class UIComponentsMixin:
|
|
|
328
334
|
Returns:
|
|
329
335
|
Breadcrumbs/Title instance.
|
|
330
336
|
"""
|
|
331
|
-
local = getattr(
|
|
337
|
+
local = getattr(cls, attr_name, None)
|
|
332
338
|
if local is not None:
|
|
333
|
-
return
|
|
339
|
+
return cls._instantiate_if_needed(local, default_cls)
|
|
334
340
|
|
|
335
341
|
if model is not None:
|
|
336
342
|
view_class = lookup.get_view_for_model(model, view_type)
|
|
337
343
|
view_component = getattr(view_class, attr_name, None)
|
|
338
|
-
return
|
|
344
|
+
return cls._instantiate_if_needed(view_component, default_cls)
|
|
339
345
|
|
|
340
346
|
return default_cls()
|
|
341
347
|
|
|
@@ -532,7 +538,8 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
|
|
|
532
538
|
form.add_error(None, msg)
|
|
533
539
|
return form
|
|
534
540
|
|
|
535
|
-
def _handle_not_implemented_error(self):
|
|
541
|
+
def _handle_not_implemented_error(self, error):
|
|
542
|
+
self.logger.debug(f"NotImplementedError raised on action {self.action} resulting in error: {error}")
|
|
536
543
|
# Blanket handler for NotImplementedError raised by form helper functions
|
|
537
544
|
msg = "Please provide the appropriate mixin before using this helper function"
|
|
538
545
|
messages.error(self.request, msg)
|
|
@@ -567,8 +574,8 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
|
|
|
567
574
|
self._handle_validation_error(e)
|
|
568
575
|
except ObjectDoesNotExist:
|
|
569
576
|
form = self._handle_object_does_not_exist(form)
|
|
570
|
-
except NotImplementedError:
|
|
571
|
-
self._handle_not_implemented_error()
|
|
577
|
+
except NotImplementedError as error:
|
|
578
|
+
self._handle_not_implemented_error(error)
|
|
572
579
|
|
|
573
580
|
if not self.has_error:
|
|
574
581
|
self.logger.debug("Form validation was successful")
|
|
@@ -730,7 +737,11 @@ class NautobotViewSetMixin(GenericViewSet, UIComponentsMixin, AccessMixin, GetRe
|
|
|
730
737
|
except TemplateDoesNotExist:
|
|
731
738
|
# Try a different detail view template format
|
|
732
739
|
template_name = f"{app_label}/{model_opts.model_name}.html"
|
|
733
|
-
|
|
740
|
+
try:
|
|
741
|
+
select_template([template_name])
|
|
742
|
+
except TemplateDoesNotExist:
|
|
743
|
+
# Catch-all fallback to just object_retrieve.html
|
|
744
|
+
template_name = "generic/object_retrieve.html"
|
|
734
745
|
return template_name
|
|
735
746
|
|
|
736
747
|
def get_form(self, *args, **kwargs):
|
|
@@ -1498,7 +1509,9 @@ class ObjectChangeLogViewMixin(NautobotViewSetMixin):
|
|
|
1498
1509
|
|
|
1499
1510
|
base_template: Optional[str] = None
|
|
1500
1511
|
|
|
1501
|
-
@drf_action(
|
|
1512
|
+
@drf_action(
|
|
1513
|
+
detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_objectchange"]
|
|
1514
|
+
)
|
|
1502
1515
|
def changelog(self, request, *args, **kwargs):
|
|
1503
1516
|
model = self.get_queryset().model
|
|
1504
1517
|
data = {
|
|
@@ -1519,7 +1532,7 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
|
|
|
1519
1532
|
|
|
1520
1533
|
base_template: Optional[str] = None
|
|
1521
1534
|
|
|
1522
|
-
@drf_action(detail=True)
|
|
1535
|
+
@drf_action(detail=True, custom_view_base_action="view", custom_view_additional_permissions=["extras.view_note"])
|
|
1523
1536
|
def notes(self, request, *args, **kwargs):
|
|
1524
1537
|
model = self.get_queryset().model
|
|
1525
1538
|
data = {
|
|
@@ -1527,3 +1540,13 @@ class ObjectNotesViewMixin(NautobotViewSetMixin):
|
|
|
1527
1540
|
"active_tab": "notes",
|
|
1528
1541
|
}
|
|
1529
1542
|
return Response(data)
|
|
1543
|
+
|
|
1544
|
+
|
|
1545
|
+
class ObjectDataComplianceViewMixin(NautobotViewSetMixin):
|
|
1546
|
+
"""
|
|
1547
|
+
UI Mixin for a DataCompliance to show up for a given object.
|
|
1548
|
+
"""
|
|
1549
|
+
|
|
1550
|
+
@drf_action(detail=True, url_path="data-compliance")
|
|
1551
|
+
def data_compliance(self, request, *args, **kwargs):
|
|
1552
|
+
return Response({})
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -15,7 +15,7 @@ from nautobot.core.forms import (
|
|
|
15
15
|
TableConfigForm,
|
|
16
16
|
)
|
|
17
17
|
from nautobot.core.forms.forms import DynamicFilterFormSet
|
|
18
|
-
from nautobot.core.templatetags.helpers import
|
|
18
|
+
from nautobot.core.templatetags.helpers import validated_viewname
|
|
19
19
|
from nautobot.core.utils.config import get_settings_or_config
|
|
20
20
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
21
21
|
from nautobot.core.utils.requests import (
|
|
@@ -227,7 +227,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
227
227
|
if view.filterset is not None:
|
|
228
228
|
filterset_filters = view.filterset.filters
|
|
229
229
|
else:
|
|
230
|
-
filterset_filters = view.filterset_class.
|
|
230
|
+
filterset_filters = view.filterset_class.base_filters
|
|
231
231
|
display_filter_params = [
|
|
232
232
|
check_filter_for_display(filterset_filters, field_name, values)
|
|
233
233
|
for field_name, values in view.filter_params.items()
|
|
@@ -324,7 +324,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
324
324
|
"action_buttons": valid_actions,
|
|
325
325
|
"list_url": list_url,
|
|
326
326
|
"saved_views": saved_views,
|
|
327
|
-
"title": bettertitle(model._meta.verbose_name_plural),
|
|
328
327
|
}
|
|
329
328
|
)
|
|
330
329
|
elif view.action in ["create", "update"]:
|
|
@@ -365,6 +364,8 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
365
364
|
# See form_valid() for self.action == "bulk_create".
|
|
366
365
|
self.template = data.get("template", view.get_template_name())
|
|
367
366
|
|
|
367
|
+
data["request"] = request
|
|
368
|
+
|
|
368
369
|
return super().render(data, accepted_media_type=accepted_media_type, renderer_context=renderer_context)
|
|
369
370
|
|
|
370
371
|
@staticmethod
|
nautobot/core/views/viewsets.py
CHANGED
|
@@ -11,9 +11,10 @@ class NautobotUIViewSet(
|
|
|
11
11
|
mixins.ObjectBulkUpdateViewMixin,
|
|
12
12
|
mixins.ObjectChangeLogViewMixin,
|
|
13
13
|
mixins.ObjectNotesViewMixin,
|
|
14
|
+
mixins.ObjectDataComplianceViewMixin,
|
|
14
15
|
):
|
|
15
16
|
"""
|
|
16
17
|
Nautobot BaseViewSet that is intended for UI use only. It provides default Nautobot functionalities such as
|
|
17
18
|
`create()`, `update()`, `partial_update()`, `bulk_update()`, `destroy()`, `bulk_destroy()`, `retrieve()`
|
|
18
|
-
`notes()`, `changelog()` and `
|
|
19
|
+
`notes()`, `changelog()`, `list()`, and `data_compliance()` actions.
|
|
19
20
|
"""
|
nautobot/data_validation/apps.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from nautobot.core.apps import NautobotConfig
|
|
2
|
-
from nautobot.extras.plugins import register_custom_validators
|
|
2
|
+
from nautobot.extras.plugins import register_custom_validators
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class DataValidationEngineConfig(NautobotConfig):
|
|
@@ -15,8 +15,4 @@ class DataValidationEngineConfig(NautobotConfig):
|
|
|
15
15
|
|
|
16
16
|
register_custom_validators(custom_validators)
|
|
17
17
|
|
|
18
|
-
from nautobot.data_validation.template_content import template_extensions
|
|
19
|
-
|
|
20
|
-
register_template_extensions(template_extensions)
|
|
21
|
-
|
|
22
18
|
import nautobot.data_validation.signals # noqa: F401 # unused-import -- but this import installs the signals
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This is the meat of this app.
|
|
3
3
|
|
|
4
|
-
Here we dynamically generate a
|
|
4
|
+
Here we dynamically generate a CustomValidator class
|
|
5
5
|
for each model currently registered in the extras_features
|
|
6
6
|
query registry 'custom_validators'.
|
|
7
7
|
|
|
@@ -40,7 +40,7 @@ LOGGER = logging.getLogger(__name__)
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class BaseValidator(CustomValidator):
|
|
43
|
-
"""Base
|
|
43
|
+
"""Base CustomValidator class that implements the core logic for enforcing validation rules defined in this app."""
|
|
44
44
|
|
|
45
45
|
model = None
|
|
46
46
|
|
|
@@ -314,10 +314,10 @@ class DataComplianceRule(CustomValidator):
|
|
|
314
314
|
|
|
315
315
|
|
|
316
316
|
class CustomValidatorIterator:
|
|
317
|
-
"""Iterator that generates
|
|
317
|
+
"""Iterator that generates CustomValidator classes for each model registered in the extras feature query registry 'custom_validators'."""
|
|
318
318
|
|
|
319
319
|
def __iter__(self):
|
|
320
|
-
"""Return a generator of
|
|
320
|
+
"""Return a generator of CustomValidator classes for each registered model."""
|
|
321
321
|
for app_label, models in registry["model_features"]["custom_validators"].items():
|
|
322
322
|
for model in models:
|
|
323
323
|
yield type(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Filtering for data_validation."""
|
|
2
2
|
|
|
3
|
-
from nautobot.apps.filters import NautobotFilterSet
|
|
4
3
|
from nautobot.core.filters import ContentTypeMultipleChoiceFilter, SearchFilter
|
|
5
4
|
from nautobot.data_validation.models import (
|
|
6
5
|
DataCompliance,
|
|
@@ -9,6 +8,7 @@ from nautobot.data_validation.models import (
|
|
|
9
8
|
RequiredValidationRule,
|
|
10
9
|
UniqueValidationRule,
|
|
11
10
|
)
|
|
11
|
+
from nautobot.extras.filters import NautobotFilterSet
|
|
12
12
|
from nautobot.extras.utils import FeatureQuery
|
|
13
13
|
|
|
14
14
|
|