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/extras/views.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from functools import partial
|
|
1
2
|
import logging
|
|
2
3
|
from typing import Optional
|
|
3
4
|
from urllib.parse import parse_qs
|
|
@@ -24,6 +25,7 @@ from django_tables2 import RequestConfig
|
|
|
24
25
|
from jsonschema.validators import Draft7Validator
|
|
25
26
|
from rest_framework.decorators import action
|
|
26
27
|
from rest_framework.permissions import IsAuthenticated
|
|
28
|
+
from rest_framework.response import Response
|
|
27
29
|
|
|
28
30
|
from nautobot.core.choices import ButtonActionColorChoices
|
|
29
31
|
from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
|
|
@@ -59,6 +61,7 @@ from nautobot.core.views.mixins import (
|
|
|
59
61
|
ObjectBulkDestroyViewMixin,
|
|
60
62
|
ObjectBulkUpdateViewMixin,
|
|
61
63
|
ObjectChangeLogViewMixin,
|
|
64
|
+
ObjectDataComplianceViewMixin,
|
|
62
65
|
ObjectDestroyViewMixin,
|
|
63
66
|
ObjectDetailViewMixin,
|
|
64
67
|
ObjectEditViewMixin,
|
|
@@ -90,6 +93,8 @@ from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
|
90
93
|
from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
|
|
91
94
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
92
95
|
from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
|
|
96
|
+
from nautobot.vpn.models import VPN, VPNProfile, VPNTunnel, VPNTunnelEndpoint
|
|
97
|
+
from nautobot.vpn.tables import VPNProfileTable, VPNTable, VPNTunnelEndpointTable, VPNTunnelTable
|
|
93
98
|
|
|
94
99
|
from . import filters, forms, jobs_ui, tables
|
|
95
100
|
from .api import serializers
|
|
@@ -323,6 +328,7 @@ class ApprovalWorkflowUIViewSet(
|
|
|
323
328
|
section=SectionChoices.RIGHT_HALF,
|
|
324
329
|
exclude_columns=["approval_workflow"],
|
|
325
330
|
add_button_route=None,
|
|
331
|
+
enable_related_link=False,
|
|
326
332
|
),
|
|
327
333
|
],
|
|
328
334
|
)
|
|
@@ -409,6 +415,7 @@ class ApprovalWorkflowStageUIViewSet(
|
|
|
409
415
|
section=SectionChoices.RIGHT_HALF,
|
|
410
416
|
exclude_columns=["approval_workflow_stage"],
|
|
411
417
|
table_title="Responses",
|
|
418
|
+
enable_related_link=False,
|
|
412
419
|
),
|
|
413
420
|
],
|
|
414
421
|
)
|
|
@@ -754,6 +761,7 @@ class ConfigContextUIViewSet(NautobotUIViewSet):
|
|
|
754
761
|
"locations",
|
|
755
762
|
"roles",
|
|
756
763
|
"device_types",
|
|
764
|
+
"device_families",
|
|
757
765
|
"platforms",
|
|
758
766
|
"cluster_groups",
|
|
759
767
|
"clusters",
|
|
@@ -964,6 +972,7 @@ class ContactUIViewSet(NautobotUIViewSet):
|
|
|
964
972
|
table_filter="contact",
|
|
965
973
|
table_title="Contact For",
|
|
966
974
|
add_button_route=None,
|
|
975
|
+
enable_related_link=False,
|
|
967
976
|
),
|
|
968
977
|
),
|
|
969
978
|
)
|
|
@@ -1262,12 +1271,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
|
|
|
1262
1271
|
elif self.action == "retrieve":
|
|
1263
1272
|
model = instance.model
|
|
1264
1273
|
table_class = get_table_for_model(model)
|
|
1265
|
-
|
|
1266
|
-
# Ensure that members cache is up-to-date for this specific group
|
|
1267
|
-
members = instance.update_cached_members()
|
|
1268
|
-
messages.success(request, f"Refreshed cached members list for {instance}")
|
|
1269
|
-
else:
|
|
1270
|
-
members = instance.members
|
|
1274
|
+
members = instance.members
|
|
1271
1275
|
if table_class is not None:
|
|
1272
1276
|
if hasattr(members, "without_tree_fields"):
|
|
1273
1277
|
members = members.without_tree_fields()
|
|
@@ -1322,7 +1326,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
|
|
|
1322
1326
|
|
|
1323
1327
|
return context
|
|
1324
1328
|
|
|
1325
|
-
def form_save(self, form, **kwargs):
|
|
1329
|
+
def form_save(self, form, commit=True, **kwargs):
|
|
1326
1330
|
obj = form.save(commit=False)
|
|
1327
1331
|
context = self.get_extra_context(self.request, obj)
|
|
1328
1332
|
|
|
@@ -1341,10 +1345,18 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
|
|
|
1341
1345
|
form.add_error(None, msg)
|
|
1342
1346
|
raise
|
|
1343
1347
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
+
if commit:
|
|
1349
|
+
# After filters have been set, now we save the object to the database.
|
|
1350
|
+
obj.save(update_cached_members=False)
|
|
1351
|
+
# Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
|
|
1352
|
+
form.save_m2m()
|
|
1353
|
+
|
|
1354
|
+
if obj.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
|
|
1355
|
+
messages.warning(
|
|
1356
|
+
self.request,
|
|
1357
|
+
"Dynamic Group membership is not automatically recalculated after creating/editing the group, "
|
|
1358
|
+
'as it may take some time to complete. You can use the "Refresh Members" button when ready.',
|
|
1359
|
+
)
|
|
1348
1360
|
|
|
1349
1361
|
# Process the formsets for children
|
|
1350
1362
|
children = context.get("children")
|
|
@@ -1359,7 +1371,7 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
|
|
|
1359
1371
|
added_errors.add(msg)
|
|
1360
1372
|
raise ValidationError("invalid DynamicGroupMembershipFormSet")
|
|
1361
1373
|
|
|
1362
|
-
if children:
|
|
1374
|
+
if commit and children:
|
|
1363
1375
|
children.save()
|
|
1364
1376
|
|
|
1365
1377
|
return obj
|
|
@@ -1658,6 +1670,7 @@ class GitRepositoryUIViewSet(NautobotUIViewSet):
|
|
|
1658
1670
|
filterset_class = filters.GitRepositoryFilterSet
|
|
1659
1671
|
serializer_class = serializers.GitRepositorySerializer
|
|
1660
1672
|
table_class = tables.GitRepositoryTable
|
|
1673
|
+
view_titles = Titles(titles={"result": "{{ object.display|default:object }} - Synchronization Status"})
|
|
1661
1674
|
|
|
1662
1675
|
def get_extra_context(self, request, instance=None):
|
|
1663
1676
|
context = super().get_extra_context(request, instance)
|
|
@@ -1701,14 +1714,18 @@ class GitRepositoryUIViewSet(NautobotUIViewSet):
|
|
|
1701
1714
|
job_result = instance.get_latest_sync()
|
|
1702
1715
|
|
|
1703
1716
|
context = {
|
|
1717
|
+
**super().get_extra_context(request, instance),
|
|
1704
1718
|
"result": job_result or {},
|
|
1705
|
-
"base_template": "extras/
|
|
1719
|
+
"base_template": "extras/gitrepository_retrieve.html",
|
|
1706
1720
|
"object": instance,
|
|
1707
1721
|
"active_tab": "result",
|
|
1708
1722
|
"verbose_name": instance._meta.verbose_name,
|
|
1709
1723
|
}
|
|
1710
1724
|
|
|
1711
|
-
return
|
|
1725
|
+
return Response(
|
|
1726
|
+
context,
|
|
1727
|
+
template_name="extras/gitrepository_result.html",
|
|
1728
|
+
)
|
|
1712
1729
|
|
|
1713
1730
|
@action(
|
|
1714
1731
|
detail=True,
|
|
@@ -1745,6 +1762,7 @@ class GraphQLQueryUIViewSet(
|
|
|
1745
1762
|
ObjectDestroyViewMixin,
|
|
1746
1763
|
ObjectBulkDestroyViewMixin,
|
|
1747
1764
|
ObjectChangeLogViewMixin,
|
|
1765
|
+
ObjectDataComplianceViewMixin,
|
|
1748
1766
|
ObjectNotesViewMixin,
|
|
1749
1767
|
):
|
|
1750
1768
|
filterset_form_class = forms.GraphQLQueryFilterForm
|
|
@@ -1755,6 +1773,27 @@ class GraphQLQueryUIViewSet(
|
|
|
1755
1773
|
table_class = tables.GraphQLQueryTable
|
|
1756
1774
|
action_buttons = ("add",)
|
|
1757
1775
|
|
|
1776
|
+
object_detail_content = object_detail.ObjectDetailContent(
|
|
1777
|
+
panels=(
|
|
1778
|
+
object_detail.ObjectFieldsPanel(
|
|
1779
|
+
label="Query",
|
|
1780
|
+
weight=100,
|
|
1781
|
+
section=SectionChoices.LEFT_HALF,
|
|
1782
|
+
fields=["name", "query", "variables"],
|
|
1783
|
+
value_transforms={
|
|
1784
|
+
"query": [lambda val: format_html('<pre><code class="language-graphql">{}</code></pre>', val)],
|
|
1785
|
+
"variables": [lambda val: helpers.render_json(val, syntax_highlight=True, pretty_print=True)],
|
|
1786
|
+
},
|
|
1787
|
+
),
|
|
1788
|
+
object_detail.Panel(
|
|
1789
|
+
weight=100,
|
|
1790
|
+
section=object_detail.SectionChoices.RIGHT_HALF,
|
|
1791
|
+
body_content_template_path="extras/inc/graphqlquery_execute.html",
|
|
1792
|
+
body_wrapper_template_path="components/panel/body_wrapper_table.html",
|
|
1793
|
+
),
|
|
1794
|
+
)
|
|
1795
|
+
)
|
|
1796
|
+
|
|
1758
1797
|
|
|
1759
1798
|
#
|
|
1760
1799
|
# Image attachments
|
|
@@ -2010,55 +2049,66 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
2010
2049
|
ignore_singleton_lock = job_form.cleaned_data.pop("_ignore_singleton_lock", False)
|
|
2011
2050
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
2012
2051
|
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
)
|
|
2026
|
-
scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
|
|
2027
|
-
is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
|
|
2028
|
-
if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
|
|
2029
|
-
messages.error(
|
|
2030
|
-
request,
|
|
2031
|
-
"Unable to run or schedule job: "
|
|
2032
|
-
"This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
|
|
2033
|
-
"Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
|
|
2052
|
+
with transaction.atomic():
|
|
2053
|
+
scheduled_job = ScheduledJob.create_schedule(
|
|
2054
|
+
job_model,
|
|
2055
|
+
request.user,
|
|
2056
|
+
name=schedule_form.cleaned_data.get("_schedule_name"),
|
|
2057
|
+
start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
|
|
2058
|
+
interval=schedule_type,
|
|
2059
|
+
crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
|
|
2060
|
+
job_queue=job_queue,
|
|
2061
|
+
profile=profile,
|
|
2062
|
+
ignore_singleton_lock=ignore_singleton_lock,
|
|
2063
|
+
**job_class.serialize_data(job_form.cleaned_data),
|
|
2034
2064
|
)
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2065
|
+
scheduled_job_has_approval_workflow = scheduled_job.has_approval_workflow_definition()
|
|
2066
|
+
is_scheduled = schedule_type in JobExecutionType.SCHEDULE_CHOICES
|
|
2067
|
+
if job_model.has_sensitive_variables and scheduled_job_has_approval_workflow:
|
|
2068
|
+
messages.error(
|
|
2069
|
+
request,
|
|
2070
|
+
"Unable to run or schedule job: "
|
|
2071
|
+
"This job is flagged as possibly having sensitive variables but also has an applicable approval workflow definition."
|
|
2072
|
+
"Modify or remove the approval workflow definition or modify the job to set `has_sensitive_variables` to False.",
|
|
2073
|
+
)
|
|
2074
|
+
scheduled_job.delete()
|
|
2075
|
+
scheduled_job = None
|
|
2076
|
+
else:
|
|
2077
|
+
if dryrun and not is_scheduled:
|
|
2078
|
+
# Enqueue job for immediate execution when dryrun and (no schedule, no has_sensitive_variables)
|
|
2079
|
+
scheduled_job.delete()
|
|
2080
|
+
scheduled_job = None
|
|
2081
|
+
return self._handle_immediate_execution(
|
|
2082
|
+
request,
|
|
2083
|
+
job_model,
|
|
2084
|
+
job_class,
|
|
2085
|
+
job_form,
|
|
2086
|
+
profile,
|
|
2087
|
+
ignore_singleton_lock,
|
|
2088
|
+
job_queue,
|
|
2089
|
+
return_url,
|
|
2090
|
+
)
|
|
2091
|
+
# Step 1: Check if approval is required
|
|
2092
|
+
if scheduled_job_has_approval_workflow:
|
|
2093
|
+
return self._handle_approval_workflow_response(request, scheduled_job, return_url)
|
|
2094
|
+
|
|
2095
|
+
# Step 3: If approval is not required
|
|
2096
|
+
if is_scheduled:
|
|
2097
|
+
return self._handle_scheduled_job_response(request, scheduled_job, return_url)
|
|
2098
|
+
|
|
2099
|
+
# Step 4: Immediate execution (no schedule, no approval)
|
|
2100
|
+
scheduled_job.delete()
|
|
2101
|
+
scheduled_job = None
|
|
2038
2102
|
return self._handle_immediate_execution(
|
|
2039
|
-
request,
|
|
2103
|
+
request,
|
|
2104
|
+
job_model,
|
|
2105
|
+
job_class,
|
|
2106
|
+
job_form,
|
|
2107
|
+
profile,
|
|
2108
|
+
ignore_singleton_lock,
|
|
2109
|
+
job_queue,
|
|
2110
|
+
return_url,
|
|
2040
2111
|
)
|
|
2041
|
-
# Step 1: Check if approval is required
|
|
2042
|
-
if scheduled_job_has_approval_workflow:
|
|
2043
|
-
scheduled_job.validated_save()
|
|
2044
|
-
return self._handle_approval_workflow_response(request, scheduled_job, return_url)
|
|
2045
|
-
|
|
2046
|
-
# Step 3: If approval is not required
|
|
2047
|
-
if is_scheduled:
|
|
2048
|
-
scheduled_job.validated_save()
|
|
2049
|
-
return self._handle_scheduled_job_response(request, scheduled_job, return_url)
|
|
2050
|
-
|
|
2051
|
-
# Step 4: Immediate execution (no schedule, no approval)
|
|
2052
|
-
return self._handle_immediate_execution(
|
|
2053
|
-
request,
|
|
2054
|
-
job_model,
|
|
2055
|
-
job_class,
|
|
2056
|
-
job_form,
|
|
2057
|
-
profile,
|
|
2058
|
-
ignore_singleton_lock,
|
|
2059
|
-
job_queue,
|
|
2060
|
-
return_url,
|
|
2061
|
-
)
|
|
2062
2112
|
|
|
2063
2113
|
if return_url:
|
|
2064
2114
|
return redirect(return_url)
|
|
@@ -2086,7 +2136,7 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
2086
2136
|
|
|
2087
2137
|
class JobView(generic.ObjectView):
|
|
2088
2138
|
queryset = JobModel.objects.all()
|
|
2089
|
-
template_name = "
|
|
2139
|
+
template_name = "generic/object_retrieve.html"
|
|
2090
2140
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
2091
2141
|
panels=[
|
|
2092
2142
|
object_detail.ObjectFieldsPanel(
|
|
@@ -2107,13 +2157,17 @@ class JobView(generic.ObjectView):
|
|
|
2107
2157
|
section=SectionChoices.LEFT_HALF,
|
|
2108
2158
|
label="Job",
|
|
2109
2159
|
fields=["grouping", "name", "description", "enabled"],
|
|
2160
|
+
value_transforms={
|
|
2161
|
+
"description": [helpers.render_markdown],
|
|
2162
|
+
},
|
|
2110
2163
|
),
|
|
2111
2164
|
object_detail.ObjectsTablePanel(
|
|
2112
2165
|
weight=100,
|
|
2113
2166
|
section=SectionChoices.FULL_WIDTH,
|
|
2114
2167
|
table_class=tables.JobResultTable,
|
|
2115
|
-
table_title="
|
|
2116
|
-
table_filter=
|
|
2168
|
+
table_title="Job Results",
|
|
2169
|
+
table_filter="job_model",
|
|
2170
|
+
exclude_columns=["name", "job_model"],
|
|
2117
2171
|
),
|
|
2118
2172
|
jobs_ui.JobObjectFieldsPanel(
|
|
2119
2173
|
weight=100,
|
|
@@ -2469,7 +2523,7 @@ class SavedViewUIViewSet(
|
|
|
2469
2523
|
|
|
2470
2524
|
|
|
2471
2525
|
class ScheduledJobListView(generic.ObjectListView):
|
|
2472
|
-
queryset = ScheduledJob.objects.
|
|
2526
|
+
queryset = ScheduledJob.objects.all()
|
|
2473
2527
|
table = tables.ScheduledJobTable
|
|
2474
2528
|
filterset = filters.ScheduledJobFilterSet
|
|
2475
2529
|
filterset_form = forms.ScheduledJobFilterForm
|
|
@@ -2701,6 +2755,12 @@ class ObjectChangeUIViewSet(ObjectDetailViewMixin, ObjectListViewMixin):
|
|
|
2701
2755
|
table_class = tables.ObjectChangeTable
|
|
2702
2756
|
action_buttons = ("export",)
|
|
2703
2757
|
|
|
2758
|
+
def __init__(self, *args, **kwargs):
|
|
2759
|
+
super().__init__(*args, **kwargs)
|
|
2760
|
+
self.object_detail_content = object_detail.ObjectDetailContent()
|
|
2761
|
+
# Remove "Advanced" tab while keeping the main.
|
|
2762
|
+
self.object_detail_content.tabs = self.object_detail_content.tabs[:1]
|
|
2763
|
+
|
|
2704
2764
|
# 2.0 TODO: Remove this remapping and solve it at the `BaseFilterSet` as it is addressing a breaking change.
|
|
2705
2765
|
def get(self, request, *args, **kwargs):
|
|
2706
2766
|
# Remappings below allow previous queries of time_before and time_after to use
|
|
@@ -2789,7 +2849,7 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
2789
2849
|
|
|
2790
2850
|
return render(
|
|
2791
2851
|
request,
|
|
2792
|
-
"
|
|
2852
|
+
"generic/object_changelog.html",
|
|
2793
2853
|
{
|
|
2794
2854
|
"object": obj,
|
|
2795
2855
|
"verbose_name": obj._meta.verbose_name,
|
|
@@ -2885,7 +2945,12 @@ class ObjectMetadataUIViewSet(
|
|
|
2885
2945
|
|
|
2886
2946
|
|
|
2887
2947
|
class NoteUIViewSet(
|
|
2888
|
-
|
|
2948
|
+
ObjectDestroyViewMixin,
|
|
2949
|
+
ObjectDetailViewMixin,
|
|
2950
|
+
ObjectEditViewMixin,
|
|
2951
|
+
ObjectListViewMixin,
|
|
2952
|
+
ObjectChangeLogViewMixin,
|
|
2953
|
+
ObjectDataComplianceViewMixin,
|
|
2889
2954
|
):
|
|
2890
2955
|
filterset_class = filters.NoteFilterSet
|
|
2891
2956
|
filterset_form_class = forms.NoteFilterForm
|
|
@@ -2996,7 +3061,7 @@ class ObjectNotesView(generic.GenericView):
|
|
|
2996
3061
|
|
|
2997
3062
|
return render(
|
|
2998
3063
|
request,
|
|
2999
|
-
"
|
|
3064
|
+
"generic/object_notes.html",
|
|
3000
3065
|
{
|
|
3001
3066
|
"object": obj,
|
|
3002
3067
|
"verbose_name": obj._meta.verbose_name,
|
|
@@ -3171,6 +3236,30 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
3171
3236
|
vdc_table.columns.hide("role")
|
|
3172
3237
|
RequestConfig(request, paginate).configure(vdc_table)
|
|
3173
3238
|
context["vdc_table"] = vdc_table
|
|
3239
|
+
if ContentType.objects.get_for_model(VPN) in context["content_types"]:
|
|
3240
|
+
vpns = instance.vpns.restrict(request.user, "view")
|
|
3241
|
+
vpn_table = VPNTable(vpns)
|
|
3242
|
+
vpn_table.columns.hide("role")
|
|
3243
|
+
RequestConfig(request, paginate).configure(vpn_table)
|
|
3244
|
+
context["vpn_table"] = vpn_table
|
|
3245
|
+
if ContentType.objects.get_for_model(VPNProfile) in context["content_types"]:
|
|
3246
|
+
vpn_profiles = instance.vpn_profiles.restrict(request.user, "view")
|
|
3247
|
+
vpn_profile_table = VPNProfileTable(vpn_profiles)
|
|
3248
|
+
vpn_profile_table.columns.hide("role")
|
|
3249
|
+
RequestConfig(request, paginate).configure(vpn_profile_table)
|
|
3250
|
+
context["vpn_profile_table"] = vpn_profile_table
|
|
3251
|
+
if ContentType.objects.get_for_model(VPNTunnel) in context["content_types"]:
|
|
3252
|
+
vpn_tunnels = instance.vpn_tunnels.restrict(request.user, "view")
|
|
3253
|
+
vpn_tunnel_table = VPNTunnelTable(vpn_tunnels)
|
|
3254
|
+
vpn_tunnel_table.columns.hide("role")
|
|
3255
|
+
RequestConfig(request, paginate).configure(vpn_tunnel_table)
|
|
3256
|
+
context["vpn_tunnel_table"] = vpn_tunnel_table
|
|
3257
|
+
if ContentType.objects.get_for_model(VPNTunnelEndpoint) in context["content_types"]:
|
|
3258
|
+
vpn_tunnel_endpoints = instance.vpn_tunnel_endpoints.restrict(request.user, "view")
|
|
3259
|
+
vpn_tunnel_endpoint_table = VPNTunnelEndpointTable(vpn_tunnel_endpoints)
|
|
3260
|
+
vpn_tunnel_endpoint_table.columns.hide("role")
|
|
3261
|
+
RequestConfig(request, paginate).configure(vpn_tunnel_endpoint_table)
|
|
3262
|
+
context["vpn_tunnel_endpoint_table"] = vpn_tunnel_endpoint_table
|
|
3174
3263
|
return context
|
|
3175
3264
|
|
|
3176
3265
|
|
|
@@ -3188,6 +3277,7 @@ class SecretUIViewSet(
|
|
|
3188
3277
|
ObjectBulkDestroyViewMixin,
|
|
3189
3278
|
# no ObjectBulkUpdateViewMixin here yet
|
|
3190
3279
|
ObjectChangeLogViewMixin,
|
|
3280
|
+
ObjectDataComplianceViewMixin,
|
|
3191
3281
|
ObjectNotesViewMixin,
|
|
3192
3282
|
):
|
|
3193
3283
|
queryset = Secret.objects.all()
|
|
@@ -3269,7 +3359,8 @@ class SecretsGroupUIViewSet(NautobotUIViewSet):
|
|
|
3269
3359
|
object_detail.ObjectsTablePanel(
|
|
3270
3360
|
table_class=tables.SecretsGroupAssociationTable,
|
|
3271
3361
|
table_filter="secrets_group",
|
|
3272
|
-
related_field_name="
|
|
3362
|
+
related_field_name="secrets_groups",
|
|
3363
|
+
related_list_url_name="extras:secret_list",
|
|
3273
3364
|
table_title="Secrets",
|
|
3274
3365
|
section=SectionChoices.LEFT_HALF,
|
|
3275
3366
|
weight=200,
|
|
@@ -3375,6 +3466,7 @@ class TagUIViewSet(NautobotUIViewSet):
|
|
|
3375
3466
|
select_related_fields=["content_type"],
|
|
3376
3467
|
prefetch_related_fields=["content_object"],
|
|
3377
3468
|
include_paginator=True,
|
|
3469
|
+
enable_related_link=False,
|
|
3378
3470
|
),
|
|
3379
3471
|
),
|
|
3380
3472
|
)
|
|
@@ -3431,6 +3523,7 @@ class TeamUIViewSet(NautobotUIViewSet):
|
|
|
3431
3523
|
table_filter="team",
|
|
3432
3524
|
table_title="Contact For",
|
|
3433
3525
|
add_button_route=None,
|
|
3526
|
+
enable_related_link=False,
|
|
3434
3527
|
),
|
|
3435
3528
|
)
|
|
3436
3529
|
)
|
|
@@ -3463,7 +3556,7 @@ class WebhookUIViewSet(NautobotUIViewSet):
|
|
|
3463
3556
|
section=SectionChoices.LEFT_HALF,
|
|
3464
3557
|
weight=100,
|
|
3465
3558
|
fields=("http_method", "http_content_type", "payload_url", "additional_headers"),
|
|
3466
|
-
value_transforms={"additional_headers": [helpers.pre_tag]},
|
|
3559
|
+
value_transforms={"additional_headers": [partial(helpers.pre_tag, format_empty_value=False)]},
|
|
3467
3560
|
),
|
|
3468
3561
|
object_detail.ObjectFieldsPanel(
|
|
3469
3562
|
label="Security",
|
|
@@ -3490,8 +3583,8 @@ class WebhookUIViewSet(NautobotUIViewSet):
|
|
|
3490
3583
|
|
|
3491
3584
|
|
|
3492
3585
|
class JobObjectChangeLogView(ObjectChangeLogView):
|
|
3493
|
-
base_template = "
|
|
3586
|
+
base_template = "generic/object_retrieve.html"
|
|
3494
3587
|
|
|
3495
3588
|
|
|
3496
3589
|
class JobObjectNotesView(ObjectNotesView):
|
|
3497
|
-
base_template = "
|
|
3590
|
+
base_template = "generic/object_retrieve.html"
|
nautobot/ipam/factory.py
CHANGED
|
@@ -278,9 +278,16 @@ class NamespaceFactory(PrimaryModelFactory):
|
|
|
278
278
|
|
|
279
279
|
class Meta:
|
|
280
280
|
model = Namespace
|
|
281
|
+
exclude = ("has_tenant", "has_description")
|
|
281
282
|
|
|
282
283
|
name = UniqueFaker("text", max_nb_chars=20) # could be up to CHARFIELD_MAX_LENGTH but that gets unwieldy fast
|
|
283
284
|
|
|
285
|
+
has_tenant = NautobotBoolIterator()
|
|
286
|
+
tenant = factory.Maybe("has_tenant", random_instance(Tenant))
|
|
287
|
+
|
|
288
|
+
has_description = NautobotBoolIterator()
|
|
289
|
+
description = factory.Maybe("has_description", factory.Faker("text", max_nb_chars=CHARFIELD_MAX_LENGTH), "")
|
|
290
|
+
|
|
284
291
|
|
|
285
292
|
class PrefixFactory(PrimaryModelFactory):
|
|
286
293
|
"""Create random Prefix objects with randomized data.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from nautobot.core.filters import NaturalKeyOrPKMultipleChoiceFilter
|
|
4
|
+
from nautobot.ipam import formfields
|
|
5
|
+
from nautobot.ipam.models import Prefix
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PrefixFilter(NaturalKeyOrPKMultipleChoiceFilter):
|
|
9
|
+
"""
|
|
10
|
+
Filter that supports filtering a foreign key to Prefix by either its PK or by a literal `prefix` string.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
field_class = formfields.PrefixFilterFormField
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs):
|
|
16
|
+
kwargs.setdefault("to_field_name", "pk")
|
|
17
|
+
kwargs.setdefault("label", "Prefix (ID or prefix string)")
|
|
18
|
+
kwargs.setdefault("queryset", Prefix.objects.all())
|
|
19
|
+
super().__init__(*args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def get_filter_predicate(self, v):
|
|
22
|
+
# Null value filtering
|
|
23
|
+
if v is None:
|
|
24
|
+
return {f"{self.field_name}__isnull": True}
|
|
25
|
+
|
|
26
|
+
# If value is a model instance, stringify it to a pk.
|
|
27
|
+
if isinstance(v, Prefix):
|
|
28
|
+
v = v.pk
|
|
29
|
+
|
|
30
|
+
# Try to cast the value to a UUID to distinguish between PKs and prefix strings
|
|
31
|
+
v = str(v)
|
|
32
|
+
try:
|
|
33
|
+
uuid.UUID(v)
|
|
34
|
+
return {self.field_name: v}
|
|
35
|
+
except (AttributeError, TypeError, ValueError):
|
|
36
|
+
# It's a prefix string
|
|
37
|
+
prefixes_queryset = Prefix.objects.net_equals(v)
|
|
38
|
+
return {f"{self.field_name}__in": prefixes_queryset.values_list("pk", flat=True)}
|
nautobot/ipam/filters.py
CHANGED
|
@@ -19,12 +19,15 @@ from nautobot.core.filters import (
|
|
|
19
19
|
SearchFilter,
|
|
20
20
|
TreeNodeMultipleChoiceFilter,
|
|
21
21
|
)
|
|
22
|
+
from nautobot.core.utils.data import is_uuid
|
|
22
23
|
from nautobot.dcim.filters import LocatableModelFilterSetMixin
|
|
23
24
|
from nautobot.dcim.models import Device, Interface, Location, VirtualDeviceContext
|
|
24
25
|
from nautobot.extras.filters import NautobotFilterSet, RoleModelFilterSetMixin, StatusModelFilterSetMixin
|
|
25
|
-
from nautobot.ipam import choices
|
|
26
|
-
from nautobot.
|
|
26
|
+
from nautobot.ipam import choices
|
|
27
|
+
from nautobot.ipam.filter_mixins import PrefixFilter
|
|
28
|
+
from nautobot.tenancy.filter_mixins import TenancyModelFilterSetMixin
|
|
27
29
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
30
|
+
from nautobot.vpn.models import VPNTunnelEndpoint
|
|
28
31
|
|
|
29
32
|
from .models import (
|
|
30
33
|
IPAddress,
|
|
@@ -57,40 +60,7 @@ __all__ = (
|
|
|
57
60
|
)
|
|
58
61
|
|
|
59
62
|
|
|
60
|
-
class
|
|
61
|
-
"""
|
|
62
|
-
Filter that supports filtering a foreign key to Prefix by either its PK or by a literal `prefix` string.
|
|
63
|
-
"""
|
|
64
|
-
|
|
65
|
-
field_class = formfields.PrefixFilterFormField
|
|
66
|
-
|
|
67
|
-
def __init__(self, *args, **kwargs):
|
|
68
|
-
kwargs.setdefault("to_field_name", "pk")
|
|
69
|
-
kwargs.setdefault("label", "Prefix (ID or prefix string)")
|
|
70
|
-
kwargs.setdefault("queryset", Prefix.objects.all())
|
|
71
|
-
super().__init__(*args, **kwargs)
|
|
72
|
-
|
|
73
|
-
def get_filter_predicate(self, v):
|
|
74
|
-
# Null value filtering
|
|
75
|
-
if v is None:
|
|
76
|
-
return {f"{self.field_name}__isnull": True}
|
|
77
|
-
|
|
78
|
-
# If value is a model instance, stringify it to a pk.
|
|
79
|
-
if isinstance(v, Prefix):
|
|
80
|
-
v = v.pk
|
|
81
|
-
|
|
82
|
-
# Try to cast the value to a UUID to distinguish between PKs and prefix strings
|
|
83
|
-
v = str(v)
|
|
84
|
-
try:
|
|
85
|
-
uuid.UUID(v)
|
|
86
|
-
return {self.field_name: v}
|
|
87
|
-
except (AttributeError, TypeError, ValueError):
|
|
88
|
-
# It's a prefix string
|
|
89
|
-
prefixes_queryset = Prefix.objects.net_equals(v)
|
|
90
|
-
return {f"{self.field_name}__in": prefixes_queryset.values_list("pk", flat=True)}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class NamespaceFilterSet(NautobotFilterSet):
|
|
63
|
+
class NamespaceFilterSet(NautobotFilterSet, TenancyModelFilterSetMixin):
|
|
94
64
|
q = SearchFilter(
|
|
95
65
|
filter_predicates={
|
|
96
66
|
"name": "icontains",
|
|
@@ -263,6 +233,13 @@ class PrefixFilterSet(
|
|
|
263
233
|
method="search_contains",
|
|
264
234
|
label="Prefixes which contain this prefix or IP",
|
|
265
235
|
)
|
|
236
|
+
ancestors = NaturalKeyOrPKMultipleChoiceFilter(
|
|
237
|
+
queryset=Prefix.objects.all(),
|
|
238
|
+
prefers_id=True,
|
|
239
|
+
to_field_name="network",
|
|
240
|
+
method="filter_ancestors",
|
|
241
|
+
label="Prefixes which are ancestors of this prefix (ID or host string)",
|
|
242
|
+
)
|
|
266
243
|
vrfs = NaturalKeyOrPKMultipleChoiceFilter(
|
|
267
244
|
queryset=VRF.objects.all(),
|
|
268
245
|
to_field_name="rd",
|
|
@@ -320,13 +297,29 @@ class PrefixFilterSet(
|
|
|
320
297
|
queryset=CloudNetwork.objects.all(),
|
|
321
298
|
to_field_name="name",
|
|
322
299
|
)
|
|
300
|
+
vpn_tunnel_endpoints = NaturalKeyOrPKMultipleChoiceFilter(
|
|
301
|
+
queryset=VPNTunnelEndpoint.objects.all(),
|
|
302
|
+
to_field_name="pk",
|
|
303
|
+
label="VPN Tunnel Endpoint ID",
|
|
304
|
+
)
|
|
305
|
+
vpn_tunnel_endpoints_name_contains = django_filters.CharFilter(
|
|
306
|
+
method="filter_vpntunnelendpoint_name_contains",
|
|
307
|
+
label="VPN Tunnel Endpoint Name Contains",
|
|
308
|
+
)
|
|
323
309
|
|
|
324
310
|
class Meta:
|
|
325
311
|
model = Prefix
|
|
326
312
|
fields = ["date_allocated", "id", "prefix_length", "tags"]
|
|
327
313
|
|
|
328
314
|
def _strip_values(self, values):
|
|
329
|
-
|
|
315
|
+
result = []
|
|
316
|
+
for value in values:
|
|
317
|
+
value = value.strip()
|
|
318
|
+
if is_uuid(value):
|
|
319
|
+
result.append(Prefix.objects.get(pk=value).prefix)
|
|
320
|
+
elif value:
|
|
321
|
+
result.append(value)
|
|
322
|
+
return result
|
|
330
323
|
|
|
331
324
|
def filter_prefix(self, queryset, name, value):
|
|
332
325
|
prefixes = self._strip_values(value)
|
|
@@ -363,6 +356,13 @@ class PrefixFilterSet(
|
|
|
363
356
|
prefixes_queryset |= queryset.filter(query)
|
|
364
357
|
return prefixes_queryset
|
|
365
358
|
|
|
359
|
+
def filter_ancestors(self, queryset, name, value):
|
|
360
|
+
if not value:
|
|
361
|
+
return queryset
|
|
362
|
+
prefixes = Prefix.objects.filter(pk__in=[v.id for v in value])
|
|
363
|
+
ancestor_ids = [ancestor.id for prefix in prefixes for ancestor in prefix.ancestors()]
|
|
364
|
+
return queryset.filter(pk__in=ancestor_ids)
|
|
365
|
+
|
|
366
366
|
def generate_query_filter_present_in_vrf(self, value):
|
|
367
367
|
if isinstance(value, (str, uuid.UUID)):
|
|
368
368
|
value = VRF.objects.get(pk=value)
|
|
@@ -376,6 +376,9 @@ class PrefixFilterSet(
|
|
|
376
376
|
params = self.generate_query_filter_present_in_vrf(value)
|
|
377
377
|
return queryset.filter(params).distinct()
|
|
378
378
|
|
|
379
|
+
def filter_vpntunnelendpoint_name_contains(self, queryset, name, value):
|
|
380
|
+
return queryset.filter(vpn_tunnel_endpoints__name__contains=value)
|
|
381
|
+
|
|
379
382
|
|
|
380
383
|
class PrefixLocationAssignmentFilterSet(NautobotFilterSet):
|
|
381
384
|
q = SearchFilter(
|
|
@@ -486,6 +489,11 @@ class IPAddressFilterSet(
|
|
|
486
489
|
label="Has NAT Inside",
|
|
487
490
|
)
|
|
488
491
|
ip_version = django_filters.NumberFilter()
|
|
492
|
+
services = NaturalKeyOrPKMultipleChoiceFilter(
|
|
493
|
+
queryset=Service.objects.all(),
|
|
494
|
+
to_field_name="name",
|
|
495
|
+
label="Services (name or ID)",
|
|
496
|
+
)
|
|
489
497
|
|
|
490
498
|
class Meta:
|
|
491
499
|
model = IPAddress
|
|
@@ -505,7 +513,14 @@ class IPAddressFilterSet(
|
|
|
505
513
|
return queryset.filter(params)
|
|
506
514
|
|
|
507
515
|
def search_by_prefix(self, queryset, name, value):
|
|
508
|
-
prefixes = [
|
|
516
|
+
prefixes = []
|
|
517
|
+
for prefix in value:
|
|
518
|
+
prefix = prefix.strip()
|
|
519
|
+
if is_uuid(prefix):
|
|
520
|
+
prefixes.append(Prefix.objects.get(pk=prefix).prefix)
|
|
521
|
+
elif prefix:
|
|
522
|
+
prefixes.append(prefix)
|
|
523
|
+
|
|
509
524
|
return queryset.net_host_contained(*prefixes)
|
|
510
525
|
|
|
511
526
|
def filter_address(self, queryset, name, value):
|
nautobot/ipam/formfields.py
CHANGED
|
@@ -67,7 +67,7 @@ class IPNetworkFormField(forms.Field):
|
|
|
67
67
|
class PrefixFilterFormField(MultiMatchModelMultipleChoiceField):
|
|
68
68
|
@property
|
|
69
69
|
def filter(self):
|
|
70
|
-
from nautobot.ipam.
|
|
70
|
+
from nautobot.ipam.filter_mixins import PrefixFilter # avoid circular definition
|
|
71
71
|
|
|
72
72
|
return PrefixFilter
|
|
73
73
|
|