nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/choices.py +0 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +9 -1
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit.html +1 -1
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/circuittype.html +1 -1
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- 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 +1 -0
- nautobot/core/api/views.py +8 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
- nautobot/core/cli/migrate_deprecated_templates.py +200 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/jobs/__init__.py +6 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/settings.py +18 -12
- nautobot/core/settings.yaml +13 -7
- 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/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- 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/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +2 -3
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +1 -1
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +4 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- 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_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 +2 -3
- nautobot/core/templates/graphene/graphiql.html +7 -7
- 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/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +1 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/nav_menu.html +2 -1
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/redoc_ui.html +7 -0
- 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 +4 -4
- nautobot/core/templates/utilities/theme_preview.html +16 -3
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +52 -6
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +23 -10
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/views.py +4 -0
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +9 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +6 -7
- 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 +75 -1
- 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_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +53 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +1 -1
- nautobot/core/tests/test_utils.py +31 -4
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +142 -10
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/object_detail.py +92 -12
- nautobot/core/urls.py +12 -1
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +17 -17
- nautobot/core/utils/lookup.py +3 -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 +15 -3
- nautobot/core/views/renderers.py +2 -0
- 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 +0 -12
- 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 +0 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +16 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
- nautobot/dcim/forms.py +1 -1
- nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
- nautobot/dcim/models/device_components.py +81 -68
- nautobot/dcim/models/devices.py +13 -16
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +3 -0
- nautobot/dcim/tables/template_code.py +14 -14
- nautobot/dcim/templates/dcim/cable.html +2 -61
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_edit.html +2 -5
- nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
- nautobot/dcim/templates/dcim/cable_trace.html +1 -3
- nautobot/dcim/templates/dcim/cable_update.html +5 -0
- nautobot/dcim/templates/dcim/consoleport.html +6 -5
- nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
- nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
- nautobot/dcim/templates/dcim/device/frontports.html +1 -1
- nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
- nautobot/dcim/templates/dcim/device/inventory.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
- nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/device/powerports.html +1 -1
- nautobot/dcim/templates/dcim/device/rearports.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device/wireless.html +1 -1
- 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_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- 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 +9 -8
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +26 -6
- nautobot/dcim/templates/dcim/interface_bulk_delete.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/location_retrieve.html +1 -242
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +5 -4
- nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
- nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
- nautobot/dcim/templates/dcim/rearport.html +7 -6
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
- nautobot/dcim/tests/integration/test_controller.py +1 -0
- nautobot/dcim/tests/test_api.py +8 -0
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +12 -6
- nautobot/dcim/tests/test_models.py +63 -4
- nautobot/dcim/tests/test_views.py +63 -22
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +547 -273
- nautobot/extras/api/views.py +9 -1
- nautobot/extras/choices.py +2 -13
- 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} +14 -46
- nautobot/extras/forms/forms.py +5 -13
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/management/__init__.py +9 -0
- 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/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +22 -13
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +59 -1
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +13 -0
- 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/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -4
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +52 -68
- 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/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield.html +1 -1
- 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/graphqlquery_retrieve.html +1 -96
- 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 -37
- nautobot/extras/templates/extras/plugin_detail.html +26 -21
- 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/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/tag.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_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +8 -7
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/test_api.py +27 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +35 -3
- 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 +21 -19
- 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 +13 -31
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_views.py +31 -30
- nautobot/extras/views.py +81 -19
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +27 -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 +13 -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_update.html +15 -0
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- 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 +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +43 -1
- 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 +46 -6
- nautobot/project-static/dist/css/nautobot.css +1 -1
- nautobot/project-static/dist/css/nautobot.css.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/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.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/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -0
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +61 -28
- 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/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.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_models.py +44 -4
- 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 +209 -0
- nautobot/vpn/filters.py +233 -0
- nautobot/vpn/forms.py +486 -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 +527 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +380 -0
- nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +341 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +294 -0
- nautobot/vpn/tests/test_models.py +97 -0
- nautobot/vpn/tests/test_views.py +281 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +437 -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.0a3.dist-info}/METADATA +14 -14
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/data_validation/template_content.py +0 -42
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
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
|
|
|
@@ -15,6 +15,7 @@ from nautobot.core.forms import (
|
|
|
15
15
|
TagFilterField,
|
|
16
16
|
)
|
|
17
17
|
from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
|
|
18
|
+
from nautobot.core.utils.config import get_settings_or_config
|
|
18
19
|
from nautobot.data_validation.models import (
|
|
19
20
|
DataCompliance,
|
|
20
21
|
MinMaxValidationRule,
|
|
@@ -22,6 +23,8 @@ from nautobot.data_validation.models import (
|
|
|
22
23
|
RequiredValidationRule,
|
|
23
24
|
UniqueValidationRule,
|
|
24
25
|
)
|
|
26
|
+
from nautobot.dcim.choices import DeviceUniquenessChoices
|
|
27
|
+
from nautobot.dcim.models import Device
|
|
25
28
|
from nautobot.extras.forms import (
|
|
26
29
|
NautobotBulkEditForm,
|
|
27
30
|
NautobotFilterForm,
|
|
@@ -300,3 +303,40 @@ class DataComplianceFilterForm(BootstrapMixin, forms.Form):
|
|
|
300
303
|
required=False,
|
|
301
304
|
)
|
|
302
305
|
q = forms.CharField(required=False, label="Search")
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
#
|
|
309
|
+
# Device Constraints
|
|
310
|
+
#
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class DeviceConstraintsForm(BootstrapMixin, forms.Form):
|
|
314
|
+
DEVICE_UNIQUENESS = forms.ChoiceField(
|
|
315
|
+
choices=DeviceUniquenessChoices.CHOICES,
|
|
316
|
+
label="Device Uniqueness",
|
|
317
|
+
required=True,
|
|
318
|
+
error_messages={
|
|
319
|
+
"invalid_choice": f"Invalid value. Available options are: {', '.join(DeviceUniquenessChoices.values())}"
|
|
320
|
+
},
|
|
321
|
+
)
|
|
322
|
+
DEVICE_NAME_REQUIRED = forms.BooleanField(
|
|
323
|
+
label="Device name required (cannot be blank or null)",
|
|
324
|
+
initial=False,
|
|
325
|
+
required=False,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def __init__(self, *args, user=None, **kwargs):
|
|
329
|
+
super().__init__(*args, **kwargs)
|
|
330
|
+
|
|
331
|
+
self.fields["DEVICE_UNIQUENESS"].initial = get_settings_or_config(
|
|
332
|
+
"DEVICE_UNIQUENESS", fallback=DeviceUniquenessChoices.DEFAULT
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
device_ct = ContentType.objects.get_for_model(Device)
|
|
336
|
+
name_rule_exists = RequiredValidationRule.objects.filter(content_type=device_ct, field="name").exists()
|
|
337
|
+
|
|
338
|
+
self.fields["DEVICE_NAME_REQUIRED"].initial = name_rule_exists
|
|
339
|
+
|
|
340
|
+
if user is not None and not user.is_staff:
|
|
341
|
+
for field in self.fields.values():
|
|
342
|
+
field.disabled = True
|
|
@@ -198,12 +198,6 @@ class Migration(migrations.Migration):
|
|
|
198
198
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
|
|
199
199
|
),
|
|
200
200
|
),
|
|
201
|
-
("created", models.DateTimeField(auto_now_add=True, null=True)),
|
|
202
|
-
("last_updated", models.DateTimeField(auto_now=True, null=True)),
|
|
203
|
-
(
|
|
204
|
-
"_custom_field_data",
|
|
205
|
-
models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
|
206
|
-
),
|
|
207
201
|
("compliance_class_name", models.CharField(max_length=255)),
|
|
208
202
|
("last_validation_date", models.DateTimeField(auto_now=True)),
|
|
209
203
|
("object_id", models.UUIDField(blank=False, null=False)),
|
|
@@ -216,7 +210,6 @@ class Migration(migrations.Migration):
|
|
|
216
210
|
"content_type",
|
|
217
211
|
models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="contenttypes.contenttype"),
|
|
218
212
|
),
|
|
219
|
-
("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
|
|
220
213
|
],
|
|
221
214
|
options={
|
|
222
215
|
"verbose_name_plural": "Data Compliance",
|
|
@@ -136,9 +136,6 @@ def copy_app_data_to_core_data(apps, schema_editor):
|
|
|
136
136
|
f"""\
|
|
137
137
|
INSERT INTO data_validation_datacompliance (
|
|
138
138
|
id,
|
|
139
|
-
created,
|
|
140
|
-
last_updated,
|
|
141
|
-
_custom_field_data,
|
|
142
139
|
compliance_class_name,
|
|
143
140
|
last_validation_date,
|
|
144
141
|
content_type_id,
|
|
@@ -150,9 +147,6 @@ INSERT INTO data_validation_datacompliance (
|
|
|
150
147
|
message
|
|
151
148
|
) SELECT
|
|
152
149
|
id,
|
|
153
|
-
created,
|
|
154
|
-
last_updated,
|
|
155
|
-
_custom_field_data,
|
|
156
150
|
compliance_class_name,
|
|
157
151
|
last_validation_date,
|
|
158
152
|
content_type_id,
|
|
@@ -274,9 +268,6 @@ def copy_core_data_to_app_data(apps, schema_editor):
|
|
|
274
268
|
f"""\
|
|
275
269
|
INSERT INTO nautobot_data_validation_engine_datacompliance (
|
|
276
270
|
id,
|
|
277
|
-
created,
|
|
278
|
-
last_updated,
|
|
279
|
-
_custom_field_data,
|
|
280
271
|
compliance_class_name,
|
|
281
272
|
last_validation_date,
|
|
282
273
|
content_type_id,
|
|
@@ -288,9 +279,6 @@ INSERT INTO nautobot_data_validation_engine_datacompliance (
|
|
|
288
279
|
message
|
|
289
280
|
) SELECT
|
|
290
281
|
id,
|
|
291
|
-
created,
|
|
292
|
-
last_updated,
|
|
293
|
-
_custom_field_data,
|
|
294
282
|
compliance_class_name,
|
|
295
283
|
last_validation_date,
|
|
296
284
|
content_type_id,
|
|
@@ -9,10 +9,11 @@ from django.core.validators import MinValueValidator, ValidationError
|
|
|
9
9
|
from django.db import models
|
|
10
10
|
|
|
11
11
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
12
|
-
from nautobot.core.models import BaseManager
|
|
12
|
+
from nautobot.core.models import BaseManager, BaseModel
|
|
13
13
|
from nautobot.core.models.generics import PrimaryModel
|
|
14
14
|
from nautobot.core.models.querysets import RestrictedQuerySet
|
|
15
15
|
from nautobot.core.utils.cache import construct_cache_key
|
|
16
|
+
from nautobot.extras.models.mixins import DynamicGroupsModelMixin, NotesMixin, SavedViewMixin
|
|
16
17
|
from nautobot.extras.utils import extras_features, FeatureQuery
|
|
17
18
|
|
|
18
19
|
|
|
@@ -62,7 +63,7 @@ class ValidationRuleManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
|
62
63
|
return construct_cache_key(self, method_name="get_enabled_for_model", branch_aware=True)
|
|
63
64
|
|
|
64
65
|
|
|
65
|
-
class
|
|
66
|
+
class ValidationRuleModelMixin(models.Model):
|
|
66
67
|
"""Base model for all validation engine rule models."""
|
|
67
68
|
|
|
68
69
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
|
|
@@ -83,6 +84,8 @@ class ValidationRule(PrimaryModel):
|
|
|
83
84
|
objects = ValidationRuleManager()
|
|
84
85
|
documentation_static_path = "docs/user-guide/platform-functionality/data-validation.html"
|
|
85
86
|
|
|
87
|
+
is_data_compliance_model = False
|
|
88
|
+
|
|
86
89
|
class Meta:
|
|
87
90
|
"""Model metadata for all validation engine rule models."""
|
|
88
91
|
|
|
@@ -101,7 +104,7 @@ class ValidationRule(PrimaryModel):
|
|
|
101
104
|
"relationships",
|
|
102
105
|
"webhooks",
|
|
103
106
|
)
|
|
104
|
-
class RegularExpressionValidationRule(
|
|
107
|
+
class RegularExpressionValidationRule(ValidationRuleModelMixin, PrimaryModel):
|
|
105
108
|
"""A type of validation rule that applies a regular expression to a given model field."""
|
|
106
109
|
|
|
107
110
|
regular_expression = models.TextField()
|
|
@@ -165,7 +168,7 @@ class RegularExpressionValidationRule(ValidationRule):
|
|
|
165
168
|
"relationships",
|
|
166
169
|
"webhooks",
|
|
167
170
|
)
|
|
168
|
-
class MinMaxValidationRule(
|
|
171
|
+
class MinMaxValidationRule(ValidationRuleModelMixin, PrimaryModel):
|
|
169
172
|
"""A type of validation rule that applies min/max constraints to a given numeric model field."""
|
|
170
173
|
|
|
171
174
|
min = models.FloatField(
|
|
@@ -231,7 +234,7 @@ class MinMaxValidationRule(ValidationRule):
|
|
|
231
234
|
"relationships",
|
|
232
235
|
"webhooks",
|
|
233
236
|
)
|
|
234
|
-
class RequiredValidationRule(
|
|
237
|
+
class RequiredValidationRule(ValidationRuleModelMixin, PrimaryModel):
|
|
235
238
|
"""A type of validation rule that applies a required constraint to a given model field."""
|
|
236
239
|
|
|
237
240
|
clone_fields = ["enabled", "content_type", "error_message"]
|
|
@@ -279,7 +282,7 @@ class RequiredValidationRule(ValidationRule):
|
|
|
279
282
|
"relationships",
|
|
280
283
|
"webhooks",
|
|
281
284
|
)
|
|
282
|
-
class UniqueValidationRule(
|
|
285
|
+
class UniqueValidationRule(ValidationRuleModelMixin, PrimaryModel):
|
|
283
286
|
"""
|
|
284
287
|
A type of validation rule that applies a unique constraint to a given model field.
|
|
285
288
|
|
|
@@ -321,7 +324,11 @@ class UniqueValidationRule(ValidationRule):
|
|
|
321
324
|
raise ValidationError({"field": "This field is already unique by default."})
|
|
322
325
|
|
|
323
326
|
|
|
324
|
-
|
|
327
|
+
@extras_features(
|
|
328
|
+
"export_templates",
|
|
329
|
+
"graphql",
|
|
330
|
+
)
|
|
331
|
+
class DataCompliance(DynamicGroupsModelMixin, NotesMixin, SavedViewMixin, BaseModel):
|
|
325
332
|
"""Model to represent the results of an audit method."""
|
|
326
333
|
|
|
327
334
|
compliance_class_name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=False, null=False)
|
|
@@ -335,6 +342,8 @@ class DataCompliance(PrimaryModel):
|
|
|
335
342
|
valid = models.BooleanField(blank=False, null=False)
|
|
336
343
|
message = models.TextField(blank=True, default="")
|
|
337
344
|
|
|
345
|
+
is_data_compliance_model = False
|
|
346
|
+
|
|
338
347
|
class Meta:
|
|
339
348
|
"""Meta class for Audit model."""
|
|
340
349
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""App navigation menu items."""
|
|
2
2
|
|
|
3
3
|
from nautobot.apps.ui import NavMenuAddButton, NavMenuGroup, NavMenuItem, NavMenuTab
|
|
4
|
+
from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
|
|
4
5
|
|
|
5
6
|
menu_items = (
|
|
6
7
|
NavMenuTab(
|
|
7
8
|
name="Extensibility",
|
|
8
|
-
icon=
|
|
9
|
+
icon=NavigationIconChoices.EXTENSIBILITY,
|
|
10
|
+
weight=NavigationWeightChoices.EXTENSIBILITY,
|
|
9
11
|
groups=(
|
|
10
12
|
NavMenuGroup(
|
|
11
13
|
name="Data Validation",
|
|
@@ -60,6 +62,11 @@ menu_items = (
|
|
|
60
62
|
name="Data Compliance",
|
|
61
63
|
permissions=["data_validation.view_datacompliance"],
|
|
62
64
|
),
|
|
65
|
+
NavMenuItem(
|
|
66
|
+
link="data_validation:device-constraints",
|
|
67
|
+
name="Device Constraints",
|
|
68
|
+
permissions=["dcim.view_device"],
|
|
69
|
+
),
|
|
63
70
|
),
|
|
64
71
|
),
|
|
65
72
|
),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from django.utils.html import format_html
|
|
4
4
|
import django_tables2 as tables
|
|
5
5
|
|
|
6
|
-
from nautobot.core.tables import BaseTable, TagColumn, ToggleColumn
|
|
6
|
+
from nautobot.core.tables import BaseTable, BooleanColumn, TagColumn, ToggleColumn
|
|
7
7
|
from nautobot.data_validation.models import (
|
|
8
8
|
DataCompliance,
|
|
9
9
|
MinMaxValidationRule,
|
|
@@ -21,7 +21,9 @@ class RegularExpressionValidationRuleTable(BaseTable):
|
|
|
21
21
|
"""Base table for the RegularExpressionValidationRule model."""
|
|
22
22
|
|
|
23
23
|
pk = ToggleColumn()
|
|
24
|
-
name = tables.
|
|
24
|
+
name = tables.Column(linkify=True, order_by=("name",))
|
|
25
|
+
enabled = BooleanColumn()
|
|
26
|
+
context_processing = BooleanColumn()
|
|
25
27
|
tags = TagColumn()
|
|
26
28
|
|
|
27
29
|
class Meta(BaseTable.Meta):
|
|
@@ -60,7 +62,8 @@ class MinMaxValidationRuleTable(BaseTable):
|
|
|
60
62
|
"""Base table for the MinMaxValidationRule model."""
|
|
61
63
|
|
|
62
64
|
pk = ToggleColumn()
|
|
63
|
-
name = tables.
|
|
65
|
+
name = tables.Column(linkify=True, order_by=("name",))
|
|
66
|
+
enabled = BooleanColumn()
|
|
64
67
|
tags = TagColumn()
|
|
65
68
|
|
|
66
69
|
class Meta(BaseTable.Meta):
|
|
@@ -99,7 +102,8 @@ class RequiredValidationRuleTable(BaseTable):
|
|
|
99
102
|
"""Base table for the RequiredValidationRule model."""
|
|
100
103
|
|
|
101
104
|
pk = ToggleColumn()
|
|
102
|
-
name = tables.
|
|
105
|
+
name = tables.Column(linkify=True, order_by=("name",))
|
|
106
|
+
enabled = BooleanColumn()
|
|
103
107
|
tags = TagColumn()
|
|
104
108
|
|
|
105
109
|
class Meta(BaseTable.Meta):
|
|
@@ -134,7 +138,8 @@ class UniqueValidationRuleTable(BaseTable):
|
|
|
134
138
|
"""Base table for the UniqueValidationRule model."""
|
|
135
139
|
|
|
136
140
|
pk = ToggleColumn()
|
|
137
|
-
name = tables.
|
|
141
|
+
name = tables.Column(linkify=True, order_by=("name",))
|
|
142
|
+
enabled = BooleanColumn()
|
|
138
143
|
tags = TagColumn()
|
|
139
144
|
|
|
140
145
|
class Meta(BaseTable.Meta):
|
|
@@ -186,6 +191,7 @@ class DataComplianceTable(BaseTable):
|
|
|
186
191
|
id = tables.Column(linkify=True, verbose_name="ID")
|
|
187
192
|
validated_object = tables.RelatedLinkColumn()
|
|
188
193
|
validated_attribute = ValidatedAttributeColumn()
|
|
194
|
+
valid = BooleanColumn()
|
|
189
195
|
|
|
190
196
|
def order_validated_object(self, queryset, is_descending):
|
|
191
197
|
"""Reorder table by string representation of validated_object."""
|
|
@@ -226,6 +232,7 @@ class DataComplianceTableTab(BaseTable):
|
|
|
226
232
|
"""Base table for viewing the DataCompliance related to a single object."""
|
|
227
233
|
|
|
228
234
|
validated_attribute = ValidatedAttributeColumn()
|
|
235
|
+
valid = BooleanColumn()
|
|
229
236
|
|
|
230
237
|
class Meta(BaseTable.Meta):
|
|
231
238
|
"""Meta class for DataComplianceTableTab."""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
{% load form_helpers %}
|
|
3
|
+
{% load ui_framework %}
|
|
4
|
+
|
|
5
|
+
{% block breadcrumbs_wrapper %}{% render_breadcrumbs %}{% endblock %}
|
|
6
|
+
|
|
7
|
+
{% block content %}
|
|
8
|
+
<form action="." method="post" class="h-100 vstack">
|
|
9
|
+
{% csrf_token %}
|
|
10
|
+
<div class="alert alert-warning d-flex align-items-center" role="alert">
|
|
11
|
+
<span aria-hidden="true" class="mdi mdi-alert me-2"></span>
|
|
12
|
+
<div>
|
|
13
|
+
<strong>Warning:</strong> Changes made here affect how device uniqueness and naming rules are enforced.
|
|
14
|
+
These changes will update the Constance configuration for <code>DEVICE_UNIQUENESS</code> and will create
|
|
15
|
+
a <code>RequiredValidationRule</code> for the <code>Device</code> model if
|
|
16
|
+
<i>Device name required</i> is selected.
|
|
17
|
+
Modify these settings <strong>at your own risk</strong>.
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="row align-content-start flex-fill">
|
|
21
|
+
<div class="col-md-8 offset-md-2">
|
|
22
|
+
|
|
23
|
+
{% if form.non_field_errors %}
|
|
24
|
+
<div class="card border-danger mb-16">
|
|
25
|
+
<div class="card-header bg-danger-subtle border-danger text-body">
|
|
26
|
+
<strong>Errors</strong>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="card-body">
|
|
29
|
+
{{ form.non_field_errors }}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
{% endif %}
|
|
33
|
+
|
|
34
|
+
<div class="card mb-16">
|
|
35
|
+
<div class="card-header"><strong>Device Constraints</strong></div>
|
|
36
|
+
<div class="card-body">
|
|
37
|
+
{% render_field form.DEVICE_UNIQUENESS %}
|
|
38
|
+
{% render_field form.DEVICE_NAME_REQUIRED %}
|
|
39
|
+
{% if not request.user.is_staff %}
|
|
40
|
+
<p class="text-secondary mt-2 text-center">
|
|
41
|
+
<em>You do not have permission to modify these settings.</em>
|
|
42
|
+
</p>
|
|
43
|
+
{% endif %}
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="nb-form-sticky-footer">
|
|
49
|
+
{% if request.user.is_staff %}
|
|
50
|
+
<button type="submit" name="_update" class="btn btn-primary">
|
|
51
|
+
<span aria-hidden="true" class="mdi mdi-check me-4"></span><!--
|
|
52
|
+
-->Update
|
|
53
|
+
</button>
|
|
54
|
+
<a href="{% url 'home' %}" class="btn btn-secondary">
|
|
55
|
+
<span aria-hidden="true" class="mdi mdi-close me-4"></span><!--
|
|
56
|
+
-->Cancel
|
|
57
|
+
</a>
|
|
58
|
+
{% endif %}
|
|
59
|
+
</div>
|
|
60
|
+
</form>
|
|
61
|
+
{% endblock %}
|
|
@@ -5,11 +5,11 @@ import contextlib
|
|
|
5
5
|
from django.core.cache import cache
|
|
6
6
|
import redis.exceptions
|
|
7
7
|
|
|
8
|
-
from nautobot.data_validation.models import
|
|
8
|
+
from nautobot.data_validation.models import ValidationRuleModelMixin
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ValidationRuleTestCaseMixin:
|
|
12
|
-
model: type(
|
|
12
|
+
model: type(ValidationRuleModelMixin)
|
|
13
13
|
|
|
14
14
|
def tearDown(self):
|
|
15
15
|
"""Ensure that validation rule caches are cleared to avoid leakage into other tests."""
|
|
@@ -210,7 +210,26 @@ CREATE TABLE `nautobot_data_validation_engine_uniquevalidationrule` (
|
|
|
210
210
|
|
|
211
211
|
def _populate_tables_postgresql(ContentType, DeviceType, VLAN):
|
|
212
212
|
with connection.cursor() as cursor:
|
|
213
|
-
|
|
213
|
+
cursor.execute(
|
|
214
|
+
"""\
|
|
215
|
+
INSERT INTO nautobot_data_validation_engine_datacompliance
|
|
216
|
+
VALUES (
|
|
217
|
+
'f20e4572-84cf-4f28-9fe6-5f0f96f78d14',
|
|
218
|
+
'2025-10-21 16:07:55.123456+00',
|
|
219
|
+
'2025-10-21 16:07:55.123456+00',
|
|
220
|
+
'{}',
|
|
221
|
+
'DcimDevicetypeCustomValidator',
|
|
222
|
+
'2025-10-21 16:07:55.123456+00',
|
|
223
|
+
'96591cd4-c4d1-4d69-982d-195bcea71a2c',
|
|
224
|
+
'Juniper SRX300',
|
|
225
|
+
'part_number',
|
|
226
|
+
'',
|
|
227
|
+
'f',
|
|
228
|
+
'A device type may only contain alpha numeric, dashes, and underscore characters.',
|
|
229
|
+
%s
|
|
230
|
+
);""",
|
|
231
|
+
[ContentType.objects.get_for_model(DeviceType).id],
|
|
232
|
+
)
|
|
214
233
|
|
|
215
234
|
# Min/max validation rules
|
|
216
235
|
cursor.execute(
|
|
@@ -269,7 +288,26 @@ VALUES (
|
|
|
269
288
|
|
|
270
289
|
def _populate_tables_mysql(ContentType, DeviceType, VLAN):
|
|
271
290
|
with connection.cursor() as cursor:
|
|
272
|
-
|
|
291
|
+
cursor.execute(
|
|
292
|
+
"""\
|
|
293
|
+
INSERT INTO `nautobot_data_validation_engine_datacompliance`
|
|
294
|
+
VALUES (
|
|
295
|
+
'f20e457284cf4f289fe65f0f96f78d14',
|
|
296
|
+
'2025-10-21 16:07:55.123456',
|
|
297
|
+
'2025-10-21 16:07:55.123456',
|
|
298
|
+
'{}',
|
|
299
|
+
'DcimDevicetypeCustomValidator',
|
|
300
|
+
'2025-10-21 16:07:55.123456',
|
|
301
|
+
'96591cd4c4d14d69982d195bcea71a2c',
|
|
302
|
+
'Juniper SRX300',
|
|
303
|
+
'part_number',
|
|
304
|
+
'',
|
|
305
|
+
0,
|
|
306
|
+
'A device type may only contain alpha numeric, dashes, and underscore characters.',
|
|
307
|
+
%s
|
|
308
|
+
);""",
|
|
309
|
+
[ContentType.objects.get_for_model(DeviceType).id],
|
|
310
|
+
)
|
|
273
311
|
|
|
274
312
|
# Min/max validation rules
|
|
275
313
|
cursor.execute(
|
|
@@ -366,7 +404,7 @@ class DVEToDataValidationMigrationTestCase(MigratorTestCase):
|
|
|
366
404
|
UniqueValidationRule = self.new_state.apps.get_model("data_validation", "uniquevalidationrule")
|
|
367
405
|
|
|
368
406
|
with self.subTest("DataCompliance"):
|
|
369
|
-
self.assertEqual(DataCompliance.objects.count(),
|
|
407
|
+
self.assertEqual(DataCompliance.objects.count(), 1)
|
|
370
408
|
|
|
371
409
|
with self.subTest("MinMaxValidationRule"):
|
|
372
410
|
self.assertEqual(MinMaxValidationRule.objects.count(), 1)
|
|
@@ -397,6 +435,48 @@ class DataValidationToDVEMigrationTestCase(MigratorTestCase):
|
|
|
397
435
|
else:
|
|
398
436
|
raise ValueError(f"Unknown/unsupported database vendor {connection.vendor}")
|
|
399
437
|
|
|
438
|
+
ContentType = self.old_state.apps.get_model("contenttypes", "contenttype")
|
|
439
|
+
DeviceType = self.old_state.apps.get_model("dcim", "devicetype")
|
|
440
|
+
VLAN = self.old_state.apps.get_model("ipam", "vlan")
|
|
441
|
+
|
|
442
|
+
DataCompliance = self.old_state.apps.get_model("data_validation", "datacompliance")
|
|
443
|
+
MinMaxValidationRule = self.old_state.apps.get_model("data_validation", "minmaxvalidationrule")
|
|
444
|
+
RegularExpressionValidationRule = self.old_state.apps.get_model(
|
|
445
|
+
"data_validation", "regularexpressionvalidationrule"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
DataCompliance.objects.create(
|
|
449
|
+
compliance_class_name="DcimDevicetypeCustomValidator",
|
|
450
|
+
object_id="96591cd4-c4d1-4d69-982d-195bcea71a2c",
|
|
451
|
+
validated_object_str="Juniper SRX300",
|
|
452
|
+
validated_attribute="part_number",
|
|
453
|
+
validated_attribute_value="",
|
|
454
|
+
valid=False,
|
|
455
|
+
message="A device type may only contain alpha numeric, dashes, and underscore characters.",
|
|
456
|
+
content_type=ContentType.objects.get_for_model(DeviceType),
|
|
457
|
+
)
|
|
458
|
+
MinMaxValidationRule.objects.create(
|
|
459
|
+
name="Max VLAN ID",
|
|
460
|
+
enabled=False,
|
|
461
|
+
error_message="",
|
|
462
|
+
field="vid",
|
|
463
|
+
min=None,
|
|
464
|
+
max=3999,
|
|
465
|
+
content_type=ContentType.objects.get_for_model(VLAN),
|
|
466
|
+
)
|
|
467
|
+
RegularExpressionValidationRule.objects.create(
|
|
468
|
+
name="Device Type Part Number",
|
|
469
|
+
enabled=True,
|
|
470
|
+
error_message="A device type may only contain alpha numeric, dashes, and underscore characters.",
|
|
471
|
+
field="part_number",
|
|
472
|
+
regular_expression="^[a-zA-Z0-9_-]+$",
|
|
473
|
+
content_type=ContentType.objects.get_for_model(DeviceType),
|
|
474
|
+
context_processing=False,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# TODO: requiredvalidationrule
|
|
478
|
+
# TODO: uniquevalidationrule
|
|
479
|
+
|
|
400
480
|
def tearDown(self):
|
|
401
481
|
super().tearDown()
|
|
402
482
|
if connection.vendor == "postgresql":
|
|
@@ -19,10 +19,9 @@ class TestFailedDataComplianceRule(DataComplianceRule):
|
|
|
19
19
|
# attribute
|
|
20
20
|
raise ComplianceError(
|
|
21
21
|
{
|
|
22
|
-
"tenant": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"status": "Status",
|
|
22
|
+
"tenant": "The tenant is wrong",
|
|
23
|
+
"name": "The name is wrong",
|
|
24
|
+
"status": "The status is wrong",
|
|
26
25
|
}
|
|
27
26
|
)
|
|
28
27
|
|
|
@@ -36,6 +35,12 @@ class TestPassedDataComplianceRule(DataComplianceRule):
|
|
|
36
35
|
"""No exception means the audit passes."""
|
|
37
36
|
|
|
38
37
|
|
|
38
|
+
class TestFailedDataComplianceRuleAlt(TestFailedDataComplianceRule):
|
|
39
|
+
"""Test implementation of DataComplianceRule, for dcim.rack."""
|
|
40
|
+
|
|
41
|
+
model = "dcim.device"
|
|
42
|
+
|
|
43
|
+
|
|
39
44
|
class TestCompliance(TestCase):
|
|
40
45
|
"""Test DataComplianceRule methods."""
|
|
41
46
|
|
|
@@ -62,7 +67,7 @@ class TestCompliance(TestCase):
|
|
|
62
67
|
|
|
63
68
|
def test_audit_fail(self):
|
|
64
69
|
result = DataCompliance.objects.filter(valid=False).all()
|
|
65
|
-
self.assertEqual(len(result),
|
|
70
|
+
self.assertEqual(len(result), 4)
|
|
66
71
|
result = DataCompliance.objects.get(validated_attribute="tenant")
|
|
67
72
|
self.assertEqual(result.compliance_class_name, "TestFailedDataComplianceRule")
|
|
68
73
|
self.assertEqual(result.validated_object, self.s)
|
|
@@ -71,10 +76,10 @@ class TestCompliance(TestCase):
|
|
|
71
76
|
|
|
72
77
|
def test_validate_replaces_results(self):
|
|
73
78
|
self.assertEqual(
|
|
74
|
-
len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)),
|
|
79
|
+
len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)), 4
|
|
75
80
|
)
|
|
76
81
|
TestFailedDataComplianceRule(self.s).clean()
|
|
77
82
|
self.assertEqual(
|
|
78
83
|
len(DataCompliance.objects.filter(compliance_class_name=TestFailedDataComplianceRule.__name__)),
|
|
79
|
-
|
|
84
|
+
4,
|
|
80
85
|
)
|
|
@@ -40,9 +40,7 @@ class ValidationRuleFilterTestCaseMixin(ValidationRuleTestCaseMixin):
|
|
|
40
40
|
self.assertQuerysetEqualAndNotEmpty(self.filterset(params, self.queryset).qs, expected_queryset)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
class RegularExpressionValidationRuleFilterTestCase(
|
|
44
|
-
ValidationRuleFilterTestCaseMixin, FilterTestCases.NameOnlyFilterTestCase
|
|
45
|
-
):
|
|
43
|
+
class RegularExpressionValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
46
44
|
"""
|
|
47
45
|
Filterset test cases for the RegularExpressionValidationRule model
|
|
48
46
|
"""
|
|
@@ -52,6 +50,7 @@ class RegularExpressionValidationRuleFilterTestCase(
|
|
|
52
50
|
filterset = RegularExpressionValidationRuleFilterSet
|
|
53
51
|
generic_filter_tests = [
|
|
54
52
|
("id",),
|
|
53
|
+
("name",),
|
|
55
54
|
("regular_expression",),
|
|
56
55
|
("error_message",),
|
|
57
56
|
("field",),
|
|
@@ -91,7 +90,7 @@ class RegularExpressionValidationRuleFilterTestCase(
|
|
|
91
90
|
)
|
|
92
91
|
|
|
93
92
|
|
|
94
|
-
class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.
|
|
93
|
+
class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
95
94
|
"""
|
|
96
95
|
Filterset test cases for the MinMaxValidationRule model
|
|
97
96
|
"""
|
|
@@ -101,6 +100,7 @@ class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
|
|
|
101
100
|
filterset = MinMaxValidationRuleFilterSet
|
|
102
101
|
generic_filter_tests = [
|
|
103
102
|
("id",),
|
|
103
|
+
("name",),
|
|
104
104
|
("error_message",),
|
|
105
105
|
("field",),
|
|
106
106
|
]
|
|
@@ -144,7 +144,7 @@ class MinMaxValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
|
|
|
144
144
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.
|
|
147
|
+
class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
148
148
|
"""
|
|
149
149
|
Filterset test cases for the RequiredValidationRule model
|
|
150
150
|
"""
|
|
@@ -154,6 +154,7 @@ class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Fi
|
|
|
154
154
|
filterset = RequiredValidationRuleFilterSet
|
|
155
155
|
generic_filter_tests = [
|
|
156
156
|
("id",),
|
|
157
|
+
("name",),
|
|
157
158
|
("error_message",),
|
|
158
159
|
("field",),
|
|
159
160
|
]
|
|
@@ -189,7 +190,7 @@ class RequiredValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Fi
|
|
|
189
190
|
)
|
|
190
191
|
|
|
191
192
|
|
|
192
|
-
class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.
|
|
193
|
+
class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
193
194
|
"""
|
|
194
195
|
Filterset test cases for the UniqueValidationRule model
|
|
195
196
|
"""
|
|
@@ -199,6 +200,7 @@ class UniqueValidationRuleFilterTestCase(ValidationRuleFilterTestCaseMixin, Filt
|
|
|
199
200
|
filterset = UniqueValidationRuleFilterSet
|
|
200
201
|
generic_filter_tests = [
|
|
201
202
|
("id",),
|
|
203
|
+
("name",),
|
|
202
204
|
("error_message",),
|
|
203
205
|
("field",),
|
|
204
206
|
("max_instances",),
|