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
|
@@ -135,6 +135,61 @@ def _fix_nav_tabs_items(html: str, stats: dict, file_path=None) -> str:
|
|
|
135
135
|
return pattern.sub(ul_replacer, html)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
def _fix_dropdown_lis(html: str, stats: dict, file_path=None) -> str:
|
|
139
|
+
"""Adds 'dropdown-item' class to all <li><a>...</a></li> tags in the given HTML string."""
|
|
140
|
+
|
|
141
|
+
def a_replacer(match):
|
|
142
|
+
a_tag = match.group(0)
|
|
143
|
+
# If Django template logic is found, notify user and skip auto-fix
|
|
144
|
+
if re.search(r"{%.*%}", a_tag):
|
|
145
|
+
if "manual_nav_template_lines" not in stats:
|
|
146
|
+
stats["manual_nav_template_lines"] = []
|
|
147
|
+
# Get line number and character position of li_tag
|
|
148
|
+
html_lines = html.splitlines()
|
|
149
|
+
for i, line in enumerate(html_lines):
|
|
150
|
+
if a_tag in line:
|
|
151
|
+
# Append line number, character position and file path for easier identification
|
|
152
|
+
stats["manual_nav_template_lines"].append(
|
|
153
|
+
f"{file_path}:{i + 1}:{line.index(a_tag)} - Please review manually '{a_tag}'"
|
|
154
|
+
)
|
|
155
|
+
break
|
|
156
|
+
else:
|
|
157
|
+
stats["manual_nav_template_lines"].append(f"{file_path} - Please review manually '{a_tag}'")
|
|
158
|
+
return a_tag
|
|
159
|
+
|
|
160
|
+
# Add dropdown-item to <a>
|
|
161
|
+
class_attr_match = re.search(r"""class=(["'])(.*?)\1""", a_tag)
|
|
162
|
+
if class_attr_match:
|
|
163
|
+
classes = class_attr_match.group(2).split()
|
|
164
|
+
if not any("dropdown-item" in _class for _class in classes):
|
|
165
|
+
classes.append("dropdown-item")
|
|
166
|
+
stats["dropdown_items"] += 1
|
|
167
|
+
new_class_attr = f'class="{" ".join(classes)}"'
|
|
168
|
+
a_tag = re.sub(r"""class=(["'])(.*?)\1""", new_class_attr, a_tag, count=1)
|
|
169
|
+
else:
|
|
170
|
+
a_tag = re.sub(r"<a(\s|>)", r'<a class="dropdown-item"\1', a_tag, count=1)
|
|
171
|
+
stats["dropdown_items"] += 1
|
|
172
|
+
|
|
173
|
+
return a_tag
|
|
174
|
+
|
|
175
|
+
return re.sub("<a[^>]*>.*?</a>", a_replacer, html, flags=re.DOTALL)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _fix_dropdown_items(html: str, stats: dict, file_path=None) -> str:
|
|
179
|
+
"""Ensures that all <li> elements within <ul class="dropdown-menu"> have class="dropdown-item" as appropriate."""
|
|
180
|
+
|
|
181
|
+
def ul_replacer(ul_match):
|
|
182
|
+
ul_tag = ul_match.group(0)
|
|
183
|
+
ul_tag_new = _fix_dropdown_lis(ul_tag, stats, file_path=file_path)
|
|
184
|
+
return ul_tag_new
|
|
185
|
+
|
|
186
|
+
pattern = re.compile(
|
|
187
|
+
r"""<ul\s+class=(["'])(?:[^"']*\s)?dropdown-menu(?:\s[^"']*)?\1[^>]*>.*?</ul>""",
|
|
188
|
+
re.DOTALL | re.IGNORECASE,
|
|
189
|
+
)
|
|
190
|
+
return pattern.sub(ul_replacer, html)
|
|
191
|
+
|
|
192
|
+
|
|
138
193
|
def _fix_extra_nav_tabs_block(html_string: str, stats: dict, file_path: str) -> str:
|
|
139
194
|
"""
|
|
140
195
|
Finds {% block extra_nav_tabs %} blocks and adds nav-item/nav-link to <li> tags inside using regex.
|
|
@@ -505,6 +560,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
|
|
|
505
560
|
"extra_breadcrumbs": 0,
|
|
506
561
|
"breadcrumb_items": 0,
|
|
507
562
|
"nav_items": 0,
|
|
563
|
+
"dropdown_items": 0,
|
|
508
564
|
"panel_classes": 0,
|
|
509
565
|
"manual_nav_template_lines": [],
|
|
510
566
|
}
|
|
@@ -521,6 +577,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
|
|
|
521
577
|
"checkbox-inline": "form-check-input",
|
|
522
578
|
"close": "btn-close",
|
|
523
579
|
"control-label": "col-form-label",
|
|
580
|
+
"dropdown-menu-right": "dropdown-menu-end",
|
|
524
581
|
"form-control-static": "form-control-plaintext",
|
|
525
582
|
"form-group": "mb-10 d-flex justify-content-center",
|
|
526
583
|
"help-block": "form-text",
|
|
@@ -582,6 +639,7 @@ def convert_bootstrap_classes(html_input: str, file_path: str) -> tuple[str, dic
|
|
|
582
639
|
current_html = _convert_caret_in_span_to_mdi(current_html, stats)
|
|
583
640
|
current_html = _convert_hover_copy_buttons(current_html, stats)
|
|
584
641
|
current_html = _fix_nav_tabs_items(current_html, stats, file_path=file_path)
|
|
642
|
+
current_html = _fix_dropdown_items(current_html, stats, file_path=file_path)
|
|
585
643
|
|
|
586
644
|
return current_html, stats
|
|
587
645
|
|
|
@@ -598,7 +656,15 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
|
|
|
598
656
|
|
|
599
657
|
totals = {
|
|
600
658
|
k: 0
|
|
601
|
-
for k in [
|
|
659
|
+
for k in [
|
|
660
|
+
"replacements",
|
|
661
|
+
"extra_breadcrumbs",
|
|
662
|
+
"breadcrumb_items",
|
|
663
|
+
"nav_items",
|
|
664
|
+
"dropdown_items",
|
|
665
|
+
"panel_classes",
|
|
666
|
+
"resizing_xs",
|
|
667
|
+
]
|
|
602
668
|
}
|
|
603
669
|
# Breakpoints that are not xs do not count as failures in djlint, so we keep a separate counter
|
|
604
670
|
resizing_other = 0
|
|
@@ -663,6 +729,8 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
|
|
|
663
729
|
print(f"{stats['breadcrumb_items']} breadcrumb-items, ", end="")
|
|
664
730
|
if stats["nav_items"]:
|
|
665
731
|
print(f"{stats['nav_items']} nav-items, ", end="")
|
|
732
|
+
if stats["dropdown_items"]:
|
|
733
|
+
print(f"{stats['dropdown_items']} dropdown-items, ", end="")
|
|
666
734
|
if stats["panel_classes"]:
|
|
667
735
|
print(f"{stats['panel_classes']} panel replacements, ", end="")
|
|
668
736
|
print()
|
|
@@ -683,6 +751,7 @@ def fix_html_files_in_directory(directory: str, resize=False) -> None:
|
|
|
683
751
|
print(f"- Extra-breadcrumb fixes: {totals['extra_breadcrumbs']}")
|
|
684
752
|
print(f"- <li> in <ol.breadcrumb>: {totals['breadcrumb_items']}")
|
|
685
753
|
print(f"- <li> in <ul.nav-tabs>: {totals['nav_items']}")
|
|
754
|
+
print(f"- <a> in <ul.dropdown-menu>: {totals['dropdown_items']}")
|
|
686
755
|
print(f"- Panel class replacements: {totals['panel_classes']}")
|
|
687
756
|
print(f"- Resizing breakpoint xs: {totals['resizing_xs']}")
|
|
688
757
|
print("-------------------------------------")
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
TEMPLATE_REPLACEMENTS = {
|
|
6
|
+
# Format: new_template: [old_template1, old_template2, ...]
|
|
7
|
+
"circuits/circuit_create.html": ["circuits/circuit_edit.html"],
|
|
8
|
+
"circuits/circuittermination_create.html": ["circuits/circuittermination_edit.html"],
|
|
9
|
+
"circuits/provider_create.html": ["circuits/provider_edit.html"],
|
|
10
|
+
"circuits/provider_retrieve.html": ["circuits/provider.html"],
|
|
11
|
+
"dcim/cable_retrieve.html": ["dcim/cable.html"],
|
|
12
|
+
"dcim/cable_update.html": ["dcim/cable_edit.html"],
|
|
13
|
+
"dcim/device_create.html": ["dcim/device_edit.html"],
|
|
14
|
+
"dcim/devicetype_update.html": ["dcim/devicetype_edit.html"],
|
|
15
|
+
"dcim/location_retrieve.html": ["dcim/location.html"],
|
|
16
|
+
"dcim/location_update.html": ["dcim/location_edit.html"],
|
|
17
|
+
"dcim/rack_retrieve.html": ["dcim/rack.html"],
|
|
18
|
+
"dcim/rack_update.html": ["dcim/rack_edit.html"],
|
|
19
|
+
"dcim/rackreservation_retrieve.html": ["dcim/rackreservation.html"],
|
|
20
|
+
"dcim/virtualchassis_create.html": ["dcim/virtualchassis_add.html"],
|
|
21
|
+
"extras/configcontext_update.html": ["extras/configcontext_edit.html"],
|
|
22
|
+
"extras/configcontextschema_retrieve.html": ["extras/configcontextschema.html"],
|
|
23
|
+
"extras/configcontextschema_update.html": ["extras/configcontextschema_edit.html"],
|
|
24
|
+
"extras/customfield_update.html": ["extras/customfield_edit.html"],
|
|
25
|
+
"extras/dynamicgroup_retrieve.html": ["extras/dynamicgroup.html"],
|
|
26
|
+
"extras/dynamicgroup_update.html": ["extras/dynamicgroup_edit.html"],
|
|
27
|
+
"extras/gitrepository_retrieve.html": ["extras/gitrepository.html"],
|
|
28
|
+
"extras/gitrepository_update.html": ["extras/gitrepository_object_edit.html"],
|
|
29
|
+
"extras/graphqlquery_retrieve.html": ["extras/graphqlquery.html"],
|
|
30
|
+
"extras/jobresult_retrieve.html": ["extras/jobresult.html"],
|
|
31
|
+
"extras/note_retrieve.html": ["extras/note.html"],
|
|
32
|
+
"extras/objectchange_retrieve.html": ["extras/objectchange.html"],
|
|
33
|
+
"extras/secretsgroup_update.html": ["extras/secretsgroup_edit.html"],
|
|
34
|
+
"extras/tag_update.html": ["extras/tag_edit.html"],
|
|
35
|
+
"generic/object_bulk_create.html": ["generic/object_bulk_import.html"],
|
|
36
|
+
"generic/object_bulk_destroy.html": ["generic/object_bulk_delete.html"],
|
|
37
|
+
"generic/object_bulk_update.html": ["generic/object_bulk_edit.html"],
|
|
38
|
+
"generic/object_changelog.html": ["extras/object_changelog.html"],
|
|
39
|
+
"generic/object_create.html": ["dcim/powerpanel_edit.html", "generic/object_edit.html", "ipam/service_edit.html"],
|
|
40
|
+
"generic/object_destroy.html": ["generic/object_delete.html"],
|
|
41
|
+
"generic/object_notes.html": ["extras/object_notes.html"],
|
|
42
|
+
"generic/object_retrieve.html": [
|
|
43
|
+
"circuits/circuit.html",
|
|
44
|
+
"circuits/circuit_retrieve.html",
|
|
45
|
+
"circuits/circuittermination.html",
|
|
46
|
+
"circuits/circuittermination_retrieve.html",
|
|
47
|
+
"circuits/circuittype.html",
|
|
48
|
+
"circuits/circuittype_retrieve.html",
|
|
49
|
+
"circuits/providernetwork.html",
|
|
50
|
+
"circuits/providernetwork_retrieve.html",
|
|
51
|
+
"cloud/cloudaccount_retrieve.html",
|
|
52
|
+
"cloud/cloudnetwork_retrieve.html",
|
|
53
|
+
"cloud/cloudresourcetype_retrieve.html",
|
|
54
|
+
"cloud/cloudservice_retrieve.html",
|
|
55
|
+
"dcim/controller/base.html",
|
|
56
|
+
"dcim/controller_retrieve.html",
|
|
57
|
+
"dcim/controller_wirelessnetworks.html",
|
|
58
|
+
"dcim/controllermanageddevicegroup_retrieve.html",
|
|
59
|
+
"dcim/device/base.html",
|
|
60
|
+
"dcim/device/consoleports.html",
|
|
61
|
+
"dcim/device/consoleserverports.html",
|
|
62
|
+
"dcim/device/devicebays.html",
|
|
63
|
+
"dcim/device/frontports.html",
|
|
64
|
+
"dcim/device/interfaces.html",
|
|
65
|
+
"dcim/device/inventory.html",
|
|
66
|
+
"dcim/device/modulebays.html",
|
|
67
|
+
"dcim/device/poweroutlets.html",
|
|
68
|
+
"dcim/device/powerports.html",
|
|
69
|
+
"dcim/device/rearports.html",
|
|
70
|
+
"dcim/device/wireless.html",
|
|
71
|
+
"dcim/devicefamily_retrieve.html",
|
|
72
|
+
"dcim/deviceredundancygroup_retrieve.html",
|
|
73
|
+
"dcim/devicetype.html",
|
|
74
|
+
"dcim/devicetype_retrieve.html",
|
|
75
|
+
"dcim/interfaceredundancygroup_retrieve.html",
|
|
76
|
+
"dcim/locationtype.html",
|
|
77
|
+
"dcim/locationtype_retrieve.html",
|
|
78
|
+
"dcim/manufacturer.html",
|
|
79
|
+
"dcim/platform.html",
|
|
80
|
+
"dcim/powerfeed.html",
|
|
81
|
+
"dcim/powerfeed_retrieve.html",
|
|
82
|
+
"dcim/powerpanel.html",
|
|
83
|
+
"dcim/powerpanel_retrieve.html",
|
|
84
|
+
"dcim/rackgroup.html",
|
|
85
|
+
"dcim/softwareimagefile_retrieve.html",
|
|
86
|
+
"dcim/softwareversion_retrieve.html",
|
|
87
|
+
"dcim/virtualchassis.html",
|
|
88
|
+
"dcim/virtualchassis_retrieve.html",
|
|
89
|
+
"dcim/virtualdevicecontext_retrieve.html",
|
|
90
|
+
"extras/computedfield.html",
|
|
91
|
+
"extras/computedfield_retrieve.html",
|
|
92
|
+
"extras/configcontext.html",
|
|
93
|
+
"extras/configcontext_retrieve.html",
|
|
94
|
+
"extras/contact_retrieve.html",
|
|
95
|
+
"extras/customfield.html",
|
|
96
|
+
"extras/customfield_retrieve.html",
|
|
97
|
+
"extras/customlink.html",
|
|
98
|
+
"extras/exporttemplate.html",
|
|
99
|
+
"extras/job_detail.html",
|
|
100
|
+
"extras/jobbutton_retrieve.html",
|
|
101
|
+
"extras/jobhook.html",
|
|
102
|
+
"extras/jobqueue_retrieve.html",
|
|
103
|
+
"extras/metadatatype_retrieve.html",
|
|
104
|
+
"extras/secretsgroup.html",
|
|
105
|
+
"extras/secretsgroup_retrieve.html",
|
|
106
|
+
"extras/status.html",
|
|
107
|
+
"extras/tag.html",
|
|
108
|
+
"extras/tag_retrieve.html",
|
|
109
|
+
"extras/team_retrieve.html",
|
|
110
|
+
"generic/object_detail.html",
|
|
111
|
+
"ipam/rir.html",
|
|
112
|
+
"ipam/service.html",
|
|
113
|
+
"ipam/service_retrieve.html",
|
|
114
|
+
"ipam/vlan.html",
|
|
115
|
+
"ipam/vlan_retrieve.html",
|
|
116
|
+
"ipam/vlangroup.html",
|
|
117
|
+
"tenancy/tenant.html",
|
|
118
|
+
"virtualization/clustergroup.html",
|
|
119
|
+
"virtualization/clustertype.html",
|
|
120
|
+
"virtualization/virtualmachine.html",
|
|
121
|
+
"virtualization/virtualmachine_retrieve.html",
|
|
122
|
+
"wireless/radioprofile_retrieve.html",
|
|
123
|
+
"wireless/supporteddatarate_retrieve.html",
|
|
124
|
+
"wireless/wirelessnetwork_retrieve.html",
|
|
125
|
+
],
|
|
126
|
+
"ipam/prefix_retrieve.html": ["ipam/prefix.html"],
|
|
127
|
+
"ipam/vlan_update.html": ["ipam/vlan_edit.html"],
|
|
128
|
+
"tenancy/tenant_create.html": ["tenancy/tenant_edit.html"],
|
|
129
|
+
"tenancy/tenantgroup_retrieve.html": ["tenancy/tenantgroup.html"],
|
|
130
|
+
"virtualchassis_update.html": ["dcim/virtualchassis_edit.html"],
|
|
131
|
+
"virtualization/virtualmachine_update.html": ["virtualization/virtualmachine_edit.html"],
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def replace_template_references(content: str) -> tuple[str, bool]:
|
|
136
|
+
"""
|
|
137
|
+
Replaces references to deprecated templates with new ones.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
content: The content of the file to replace references in.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
A tuple containing the updated content and a boolean indicating if any changes were made.
|
|
144
|
+
"""
|
|
145
|
+
for new_template, old_templates in TEMPLATE_REPLACEMENTS.items():
|
|
146
|
+
for old_template in old_templates:
|
|
147
|
+
pattern = rf"(\{{%\s*extends\s*['\"]){re.escape(old_template)}(['\"]\s*%\}})"
|
|
148
|
+
new_content, count = re.subn(pattern, rf"\1{new_template}\2", content)
|
|
149
|
+
if count > 0:
|
|
150
|
+
# A django template can only have one extends statement, so we can return as soon as we find a match.
|
|
151
|
+
return new_content, True
|
|
152
|
+
|
|
153
|
+
return content, False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def replace_deprecated_templates(path: str, dry_run: bool = False):
|
|
157
|
+
"""
|
|
158
|
+
Recursively finds all .html files in the given directory,
|
|
159
|
+
and replaces references to deprecated templates with new ones.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if os.path.isfile(path):
|
|
163
|
+
only_filename = os.path.basename(path)
|
|
164
|
+
path = os.path.dirname(path)
|
|
165
|
+
else:
|
|
166
|
+
only_filename = None
|
|
167
|
+
|
|
168
|
+
for root, _, files in os.walk(path):
|
|
169
|
+
for filename in files:
|
|
170
|
+
if only_filename and only_filename != filename:
|
|
171
|
+
continue
|
|
172
|
+
if filename.endswith((".html")):
|
|
173
|
+
file_path = os.path.join(root, filename)
|
|
174
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
175
|
+
original_content = f.read()
|
|
176
|
+
|
|
177
|
+
content = original_content
|
|
178
|
+
|
|
179
|
+
fixed_content, was_updated = replace_template_references(content)
|
|
180
|
+
|
|
181
|
+
if was_updated:
|
|
182
|
+
if dry_run:
|
|
183
|
+
print(f"Detected deprecated template reference in {file_path}")
|
|
184
|
+
continue
|
|
185
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
186
|
+
f.write(fixed_content)
|
|
187
|
+
print(f"Updated: {file_path}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
parser = argparse.ArgumentParser(description="Replace deprecated templates with new ones")
|
|
192
|
+
parser.add_argument("path", type=str, help="Path to directory in which to recursively fix all .html files.")
|
|
193
|
+
parser.add_argument("--dry-run", action="store_true", help="Do not make any changes to the files.")
|
|
194
|
+
args = parser.parse_args()
|
|
195
|
+
|
|
196
|
+
replace_deprecated_templates(args.path, args.dry_run)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
main()
|
nautobot/core/constants.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from urllib.parse import urlparse
|
|
2
|
+
|
|
1
3
|
from django.conf import settings as django_settings
|
|
2
4
|
from django.urls import NoReverseMatch, reverse
|
|
3
5
|
|
|
@@ -78,6 +80,12 @@ def nav_menu(request):
|
|
|
78
80
|
pass
|
|
79
81
|
|
|
80
82
|
nav_menu_object = {"tabs": {}}
|
|
83
|
+
|
|
84
|
+
if htmx_current_url := request.headers.get("HX-Current-URL"):
|
|
85
|
+
current_url = urlparse(htmx_current_url).path
|
|
86
|
+
else:
|
|
87
|
+
current_url = request.path
|
|
88
|
+
|
|
81
89
|
for tab_name, tab_details in registry["nav_menu"]["tabs"].items():
|
|
82
90
|
if not tab_details["permissions"] or has_one_or_more_perms(request.user, tab_details["permissions"]):
|
|
83
91
|
nav_menu_object["tabs"][tab_name] = {"groups": {}, "icon": tab_details["icon"]}
|
|
@@ -93,7 +101,7 @@ def nav_menu(request):
|
|
|
93
101
|
if has_identified_active_link:
|
|
94
102
|
is_active = False
|
|
95
103
|
else:
|
|
96
|
-
is_active = item_link in [
|
|
104
|
+
is_active = item_link in [current_url, related_list_view_link]
|
|
97
105
|
if is_active:
|
|
98
106
|
has_identified_active_link = True
|
|
99
107
|
|
nautobot/core/forms/forms.py
CHANGED
|
@@ -377,7 +377,7 @@ class DynamicFilterForm(BootstrapMixin, forms.Form):
|
|
|
377
377
|
|
|
378
378
|
def _get_lookup_field_choices(self):
|
|
379
379
|
"""Get choices for lookup_fields i.e filterset parameters without a lookup expr"""
|
|
380
|
-
from nautobot.extras.
|
|
380
|
+
from nautobot.extras.filter_mixins import RelationshipFilter # Avoid circular import
|
|
381
381
|
|
|
382
382
|
filterset_without_lookup = (
|
|
383
383
|
(
|
nautobot/core/jobs/__init__.py
CHANGED
|
@@ -22,7 +22,7 @@ from nautobot.core.celery import app, register_jobs
|
|
|
22
22
|
from nautobot.core.exceptions import AbortTransaction
|
|
23
23
|
from nautobot.core.jobs.bulk_actions import BulkDeleteObjects, BulkEditObjects
|
|
24
24
|
from nautobot.core.jobs.cleanup import LogsCleanup
|
|
25
|
-
from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
|
|
25
|
+
from nautobot.core.jobs.groups import RefreshDynamicGroupCacheJobButtonReceiver, RefreshDynamicGroupCaches
|
|
26
26
|
from nautobot.core.utils.lookup import get_filterset_for_model
|
|
27
27
|
from nautobot.core.utils.requests import get_filterable_params_from_filter_params
|
|
28
28
|
from nautobot.data_validation import models
|
|
@@ -440,8 +440,10 @@ def clean_compliance_rules_results_for_instance(instance, excluded_pks):
|
|
|
440
440
|
class RunRegisteredDataComplianceRules(Job):
|
|
441
441
|
"""Run the validate function on all registered DataComplianceRule classes and, optionally, the built-in data validation rules."""
|
|
442
442
|
|
|
443
|
-
|
|
444
|
-
|
|
443
|
+
class Meta:
|
|
444
|
+
name = "Run Registered Data Compliance Rules"
|
|
445
|
+
description = "Runs selected Data Compliance rule classes."
|
|
446
|
+
has_sensitive_variables = False
|
|
445
447
|
|
|
446
448
|
selected_data_compliance_rules = MultiChoiceVar(
|
|
447
449
|
choices=get_data_compliance_choices,
|
|
@@ -536,6 +538,7 @@ jobs = [
|
|
|
536
538
|
ImportObjects,
|
|
537
539
|
LogsCleanup,
|
|
538
540
|
RefreshDynamicGroupCaches,
|
|
541
|
+
RefreshDynamicGroupCacheJobButtonReceiver,
|
|
539
542
|
RunRegisteredDataComplianceRules,
|
|
540
543
|
]
|
|
541
544
|
register_jobs(*jobs)
|
nautobot/core/jobs/groups.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from nautobot.extras.choices import DynamicGroupTypeChoices
|
|
2
|
-
from nautobot.extras.jobs import Job, ObjectVar
|
|
2
|
+
from nautobot.extras.jobs import Job, JobButtonReceiver, ObjectVar
|
|
3
3
|
from nautobot.extras.models import DynamicGroup
|
|
4
4
|
|
|
5
5
|
name = "System Jobs"
|
|
@@ -31,8 +31,38 @@ class RefreshDynamicGroupCaches(Job):
|
|
|
31
31
|
if single_group is not None:
|
|
32
32
|
groups = groups.filter(pk=single_group.pk)
|
|
33
33
|
|
|
34
|
+
if not groups.exists():
|
|
35
|
+
self.logger.info("No relevant dynamic groups were specified, nothing to do.")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
self.logger.info("Re-calculating and re-caching group members. This may take some time.")
|
|
34
39
|
for group in groups:
|
|
35
40
|
group.update_cached_members()
|
|
36
41
|
self.logger.info("Cache refreshed successfully, now with %d members", group.count, extra={"object": group})
|
|
37
42
|
|
|
38
43
|
self.logger.info("Cache(s) refreshed")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RefreshDynamicGroupCacheJobButtonReceiver(JobButtonReceiver):
|
|
47
|
+
"""
|
|
48
|
+
System Job Button Receiver to re-calculate and re-cache the members of a given Dynamic Group.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
class Meta:
|
|
52
|
+
name = "Refresh Dynamic Group Cache (Job Button Receiver)"
|
|
53
|
+
description = "Re-calculate and re-cache the membership list of a given Dynamic Group."
|
|
54
|
+
|
|
55
|
+
def receive_job_button(self, obj):
|
|
56
|
+
if not isinstance(obj, DynamicGroup):
|
|
57
|
+
self.fail("This job button should only be used with Dynamic Group records.")
|
|
58
|
+
elif obj.group_type == DynamicGroupTypeChoices.TYPE_STATIC:
|
|
59
|
+
self.fail(
|
|
60
|
+
"The members of this Dynamic Group are statically defined and do not need to be recalculated.",
|
|
61
|
+
extra={"object": obj},
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
self.logger.info(
|
|
65
|
+
"Re-calculating and re-caching group members. This may take some time.", extra={"object": obj}
|
|
66
|
+
)
|
|
67
|
+
obj.update_cached_members()
|
|
68
|
+
self.logger.success("Cache refreshed successfully, now with %d members", obj.count, extra={"object": obj})
|
|
@@ -133,6 +133,14 @@ class Command(BaseCommand):
|
|
|
133
133
|
)
|
|
134
134
|
from nautobot.tenancy.factory import TenantFactory, TenantGroupFactory
|
|
135
135
|
from nautobot.users.factory import UserFactory
|
|
136
|
+
from nautobot.vpn.factory import (
|
|
137
|
+
VPNFactory,
|
|
138
|
+
VPNPhase1PolicyFactory,
|
|
139
|
+
VPNPhase2PolicyFactory,
|
|
140
|
+
VPNProfileFactory,
|
|
141
|
+
VPNTunnelEndpointFactory,
|
|
142
|
+
VPNTunnelFactory,
|
|
143
|
+
)
|
|
136
144
|
from nautobot.wireless.factory import (
|
|
137
145
|
ControllerManagedDeviceGroupWithMembersFactory,
|
|
138
146
|
RadioProfileFactory,
|
|
@@ -171,14 +179,12 @@ class Command(BaseCommand):
|
|
|
171
179
|
)
|
|
172
180
|
# ...and some tags that apply to a random subset of content-types
|
|
173
181
|
_create_batch(TagFactory, 15, description="on some content-types")
|
|
174
|
-
_create_batch(UserFactory,
|
|
182
|
+
_create_batch(UserFactory, 10)
|
|
175
183
|
_create_batch(SavedViewFactory, 20)
|
|
176
184
|
_create_batch(ContactFactory, 20)
|
|
177
185
|
_create_batch(TeamFactory, 20)
|
|
178
|
-
_create_batch(TenantGroupFactory,
|
|
179
|
-
_create_batch(
|
|
180
|
-
_create_batch(TenantFactory, 10, description="without a parent group", has_tenant_group=False)
|
|
181
|
-
_create_batch(TenantFactory, 10, description="with a parent group", has_tenant_group=True)
|
|
186
|
+
_create_batch(TenantGroupFactory, 30)
|
|
187
|
+
_create_batch(TenantFactory, 30)
|
|
182
188
|
_create_batch(LocationTypeFactory, 7) # only 7 unique LocationTypes are hard-coded presently
|
|
183
189
|
# First 7 locations must be created in specific order so subsequent objects have valid parents to reference
|
|
184
190
|
_create_batch(LocationFactory, 7, description="as structure", has_parent=True)
|
|
@@ -192,7 +198,7 @@ class Command(BaseCommand):
|
|
|
192
198
|
_create_batch(VRFFactory, 20)
|
|
193
199
|
_create_batch(VLANGroupFactory, 20)
|
|
194
200
|
_create_batch(VLANFactory, 20)
|
|
195
|
-
for i in range(
|
|
201
|
+
for i in range(50):
|
|
196
202
|
_create_batch(
|
|
197
203
|
PrefixFactory,
|
|
198
204
|
1,
|
|
@@ -207,7 +213,15 @@ class Command(BaseCommand):
|
|
|
207
213
|
prefix=f"2001:db8:0:{i}::/64",
|
|
208
214
|
type=PrefixTypeChoices.TYPE_CONTAINER,
|
|
209
215
|
)
|
|
210
|
-
_create_batch(
|
|
216
|
+
_create_batch(
|
|
217
|
+
NamespaceFactory, 5, description="with a Tenant and without any Prefixes or IPAddresses", has_tenant=True
|
|
218
|
+
)
|
|
219
|
+
_create_batch(
|
|
220
|
+
NamespaceFactory,
|
|
221
|
+
5,
|
|
222
|
+
description="without a Tenant and without any Prefixes or IPAddresses",
|
|
223
|
+
has_tenant=False,
|
|
224
|
+
)
|
|
211
225
|
_create_batch(DeviceFamilyFactory, 20)
|
|
212
226
|
_create_batch(ManufacturerFactory, 8) # First 8 hard-coded Manufacturers
|
|
213
227
|
_create_batch(PlatformFactory, 20, description="with Manufacturers", has_manufacturer=True)
|
|
@@ -222,7 +236,7 @@ class Command(BaseCommand):
|
|
|
222
236
|
_create_batch(ConsoleServerPortTemplateFactory, 30)
|
|
223
237
|
_create_batch(RearPortTemplateFactory, 30)
|
|
224
238
|
_create_batch(FrontPortTemplateFactory, 30)
|
|
225
|
-
_create_batch(InterfaceTemplateFactory,
|
|
239
|
+
_create_batch(InterfaceTemplateFactory, 50)
|
|
226
240
|
_create_batch(PowerPortTemplateFactory, 30)
|
|
227
241
|
_create_batch(PowerOutletTemplateFactory, 30)
|
|
228
242
|
_create_batch(ModuleBayTemplateFactory, 60, description="without module families", has_module_family=False)
|
|
@@ -324,11 +338,16 @@ class Command(BaseCommand):
|
|
|
324
338
|
_create_batch(WirelessNetworksWithMembersFactory, 5, description="with members")
|
|
325
339
|
# make sure we have some supported data rates that have null relationships to make filter tests happy
|
|
326
340
|
_create_batch(SupportedDataRateFactory, 10, description="without any associated objects")
|
|
341
|
+
_create_batch(VPNPhase1PolicyFactory, 10)
|
|
342
|
+
_create_batch(VPNPhase2PolicyFactory, 10)
|
|
343
|
+
_create_batch(VPNProfileFactory, 20)
|
|
344
|
+
_create_batch(VPNFactory, 10)
|
|
345
|
+
_create_batch(VPNTunnelEndpointFactory, 20)
|
|
346
|
+
_create_batch(VPNTunnelFactory, 10)
|
|
327
347
|
_create_batch(JobQueueFactory, 10)
|
|
328
348
|
# make sure we have some tenants that have null relationships to make filter tests happy
|
|
329
349
|
_create_batch(TenantFactory, 10, description="without any associated objects")
|
|
330
350
|
# TODO: nautobot.tenancy.tests.test_filters currently calls the following additional factories:
|
|
331
|
-
# _create_batch(UserFactory, 10)
|
|
332
351
|
# _create_batch(RackFactory, 10)
|
|
333
352
|
# _create_batch(RackReservationFactory, 10)
|
|
334
353
|
# _create_batch(ClusterTypeFactory, 10)
|
nautobot/core/models/generics.py
CHANGED
|
@@ -4,13 +4,20 @@ from nautobot.core.models import BaseModel
|
|
|
4
4
|
from nautobot.core.models.fields import TagsField
|
|
5
5
|
from nautobot.extras.models.change_logging import ChangeLoggedModel
|
|
6
6
|
from nautobot.extras.models.customfields import CustomFieldModel
|
|
7
|
-
from nautobot.extras.models.mixins import
|
|
7
|
+
from nautobot.extras.models.mixins import (
|
|
8
|
+
ContactMixin,
|
|
9
|
+
DataComplianceModelMixin,
|
|
10
|
+
DynamicGroupsModelMixin,
|
|
11
|
+
NotesMixin,
|
|
12
|
+
SavedViewMixin,
|
|
13
|
+
)
|
|
8
14
|
from nautobot.extras.models.relationships import RelationshipModel
|
|
9
15
|
|
|
10
16
|
logger = logging.getLogger(__name__)
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class OrganizationalModel(
|
|
20
|
+
DataComplianceModelMixin,
|
|
14
21
|
ChangeLoggedModel,
|
|
15
22
|
ContactMixin,
|
|
16
23
|
CustomFieldModel,
|
|
@@ -35,6 +42,7 @@ class OrganizationalModel(
|
|
|
35
42
|
|
|
36
43
|
|
|
37
44
|
class PrimaryModel(
|
|
45
|
+
DataComplianceModelMixin,
|
|
38
46
|
ChangeLoggedModel,
|
|
39
47
|
ContactMixin,
|
|
40
48
|
CustomFieldModel,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
1
3
|
from django.core.cache import cache
|
|
2
4
|
from django.db.models import Case, When
|
|
3
5
|
from django.db.models.signals import post_delete, post_save
|
|
@@ -21,15 +23,18 @@ class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
|
|
|
21
23
|
Dynamically computes ancestors either through the tree or through the `parent` foreign key depending on whether
|
|
22
24
|
tree fields are present on `of`.
|
|
23
25
|
"""
|
|
26
|
+
|
|
27
|
+
# If `of` is a UUID, i.e. pk, retrieve the corresponding model instance with tree fields disabled.
|
|
28
|
+
if isinstance(of, uuid.UUID):
|
|
29
|
+
of = self.model.objects.without_tree_fields().get(pk=of)
|
|
30
|
+
|
|
24
31
|
# If `of` has `tree_depth` defined, i.e. if it was retrieved from the database on a queryset where tree fields
|
|
25
32
|
# were enabled (see `TreeQuerySet.with_tree_fields` and `TreeQuerySet.without_tree_fields`), use the default
|
|
26
33
|
# implementation from `tree_queries.query.TreeQuerySet`.
|
|
27
|
-
|
|
28
|
-
# will then annotate the tree fields and proceed as usual.
|
|
29
|
-
if hasattr(of, "tree_depth") or not hasattr(of, "parent"):
|
|
34
|
+
if hasattr(of, "tree_depth"):
|
|
30
35
|
return super().ancestors(of, include_self=include_self)
|
|
36
|
+
|
|
31
37
|
# In the other case, traverse the `parent` foreign key until the root.
|
|
32
|
-
model_class = of._meta.concrete_model
|
|
33
38
|
ancestor_pks = []
|
|
34
39
|
if include_self:
|
|
35
40
|
ancestor_pks.append(of.pk)
|
|
@@ -40,7 +45,7 @@ class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
|
|
|
40
45
|
# Reference:
|
|
41
46
|
# https://stackoverflow.com/questions/4916851/django-get-a-queryset-from-array-of-ids-in-specific-order
|
|
42
47
|
preserve_order = Case(*[When(pk=pk, then=position) for position, pk in enumerate(ancestor_pks)])
|
|
43
|
-
return
|
|
48
|
+
return self.model.objects.without_tree_fields().filter(pk__in=ancestor_pks).order_by(preserve_order)
|
|
44
49
|
|
|
45
50
|
def max_tree_depth(self):
|
|
46
51
|
r"""
|
nautobot/core/settings.py
CHANGED
|
@@ -95,11 +95,12 @@ if "NAUTOBOT_DEPLOYMENT_ID" in os.environ and os.environ["NAUTOBOT_DEPLOYMENT_ID
|
|
|
95
95
|
DEPLOYMENT_ID = os.environ["NAUTOBOT_DEPLOYMENT_ID"]
|
|
96
96
|
|
|
97
97
|
# Device names are not guaranteed globally-unique by Nautobot but in practice they often are.
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
# Select how Devices are uniquely identified:
|
|
99
|
+
# - 'location_tenant_name': combination of Location + Tenant + Name
|
|
100
|
+
# - 'name': Device name must be globally unique
|
|
101
|
+
# - 'none': No enforced uniqueness (rely on other validation rules or custom validators)
|
|
102
|
+
if "NAUTOBOT_DEVICE_UNIQUENESS" in os.environ and os.environ["NAUTOBOT_DEVICE_UNIQUENESS"] != "":
|
|
103
|
+
DEVICE_UNIQUENESS = os.environ["NAUTOBOT_DEVICE_UNIQUENESS"]
|
|
103
104
|
|
|
104
105
|
# Event Brokers
|
|
105
106
|
EVENT_BROKERS = {}
|
|
@@ -416,6 +417,7 @@ SPECTACULAR_SETTINGS = {
|
|
|
416
417
|
# result in this error:
|
|
417
418
|
# enum naming encountered a non-optimally resolvable collision for fields named "mode".
|
|
418
419
|
"InterfaceModeChoices": "nautobot.dcim.choices.InterfaceModeChoices",
|
|
420
|
+
"VPNPhase2PolicyChoices": "nautobot.vpn.choices.DhGroupChoices",
|
|
419
421
|
"WirelessNetworkModeChoices": "nautobot.wireless.choices.WirelessNetworkModeChoices",
|
|
420
422
|
},
|
|
421
423
|
# Create separate schema components for PATCH requests (fields generally are not `required` on PATCH)
|
|
@@ -569,6 +571,7 @@ INSTALLED_APPS = [
|
|
|
569
571
|
"nautobot.tenancy",
|
|
570
572
|
"nautobot.users",
|
|
571
573
|
"nautobot.virtualization",
|
|
574
|
+
"nautobot.vpn",
|
|
572
575
|
"nautobot.wireless",
|
|
573
576
|
"drf_spectacular",
|
|
574
577
|
"drf_spectacular_sidecar",
|
|
@@ -759,12 +762,15 @@ CONSTANCE_CONFIG = {
|
|
|
759
762
|
help_text="Number of days to retain object changelog history.\nSet this to 0 to retain changes indefinitely.",
|
|
760
763
|
field_type=int,
|
|
761
764
|
),
|
|
762
|
-
"
|
|
763
|
-
default=
|
|
764
|
-
help_text=
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
765
|
+
"DEVICE_UNIQUENESS": ConstanceConfigItem(
|
|
766
|
+
default="location_tenant_name",
|
|
767
|
+
help_text=(
|
|
768
|
+
"Select how Devices are uniquely identified:\n"
|
|
769
|
+
"- 'location_tenant_name': combination of Location + Tenant + Name\n"
|
|
770
|
+
"- 'name': Device name must be globally unique\n"
|
|
771
|
+
"- 'none': No enforced uniqueness (rely on other validation rules or custom validators)"
|
|
772
|
+
),
|
|
773
|
+
field_type=str,
|
|
768
774
|
),
|
|
769
775
|
"DEPLOYMENT_ID": ConstanceConfigItem(
|
|
770
776
|
default="",
|
|
@@ -869,7 +875,7 @@ CONSTANCE_CONFIG_FIELDSETS = {
|
|
|
869
875
|
"Change Logging": ["CHANGELOG_RETENTION"],
|
|
870
876
|
"Device Connectivity": ["NETWORK_DRIVERS", "PREFER_IPV4"],
|
|
871
877
|
"Installation Metrics": ["DEPLOYMENT_ID"],
|
|
872
|
-
"Natural Keys": ["
|
|
878
|
+
"Natural Keys": ["DEVICE_UNIQUENESS", "LOCATION_NAME_AS_NATURAL_KEY"],
|
|
873
879
|
"Pagination": ["PAGINATE_COUNT", "MAX_PAGE_SIZE", "PER_PAGE_DEFAULTS"],
|
|
874
880
|
"Performance": ["JOB_CREATE_FILE_MAX_SIZE"],
|
|
875
881
|
"Rack Elevation Rendering": [
|