nautobot 2.4.21__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 +2 -2
- nautobot/apps/filters.py +9 -9
- nautobot/apps/forms.py +2 -0
- nautobot/apps/models.py +7 -2
- nautobot/apps/ui.py +20 -1
- nautobot/apps/utils.py +2 -3
- nautobot/apps/views.py +7 -1
- nautobot/circuits/filters.py +8 -23
- nautobot/circuits/navigation.py +3 -1
- nautobot/circuits/templates/circuits/circuit_create.html +9 -9
- nautobot/circuits/templates/circuits/circuit_terminations_swap.html +2 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +24 -33
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +10 -10
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +13 -13
- nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +6 -6
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +3 -3
- nautobot/circuits/templates/circuits/inc/speed_widget.html +13 -13
- nautobot/circuits/templates/circuits/provider_create.html +9 -9
- nautobot/circuits/tests/integration/test_circuit.py +19 -19
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +3 -0
- nautobot/circuits/tests/integration/test_relationships.py +4 -12
- nautobot/circuits/views.py +0 -2
- nautobot/cloud/filters.py +1 -13
- nautobot/cloud/navigation.py +3 -1
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +9 -9
- nautobot/cloud/templates/cloud/cloudservice_update.html +6 -6
- nautobot/core/api/fields.py +30 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +9 -2
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +58 -37
- nautobot/core/apps/__init__.py +6 -12
- nautobot/core/branching.py +83 -0
- nautobot/core/celery/__init__.py +11 -6
- nautobot/core/celery/backends.py +2 -0
- nautobot/core/celery/encoders.py +7 -0
- nautobot/core/celery/task.py +44 -0
- nautobot/core/checks.py +60 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +776 -0
- nautobot/core/constants.py +9 -0
- nautobot/core/context_processors.py +84 -0
- nautobot/core/filters.py +131 -2
- nautobot/core/forms/__init__.py +4 -2
- nautobot/core/forms/fields.py +10 -8
- nautobot/core/forms/forms.py +21 -9
- nautobot/core/forms/search.py +0 -15
- nautobot/core/forms/widgets.py +3 -2
- nautobot/core/graphql/__init__.py +8 -26
- nautobot/core/graphql/generators.py +16 -6
- nautobot/core/graphql/schema.py +1 -1
- nautobot/core/graphql/schema_init.py +1 -2
- nautobot/core/graphql/utils.py +7 -9
- nautobot/core/jobs/__init__.py +158 -0
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +17 -2
- nautobot/core/models/fields.py +3 -2
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/name_color_content_types.py +1 -1
- nautobot/core/models/ordering.py +7 -5
- nautobot/core/models/querysets.py +77 -2
- nautobot/core/models/tree_queries.py +6 -4
- nautobot/core/settings.py +30 -16
- nautobot/core/settings.yaml +13 -7
- nautobot/core/tables.py +114 -44
- nautobot/core/templates/403.html +1 -1
- nautobot/core/templates/403_csrf_failure.html +1 -1
- nautobot/core/templates/404.html +1 -1
- nautobot/core/templates/40x.html +8 -8
- nautobot/core/templates/500.html +10 -10
- nautobot/core/templates/about.html +13 -12
- nautobot/core/templates/admin/actions.html +1 -1
- nautobot/core/templates/admin/app_index.html +3 -3
- nautobot/core/templates/admin/base.html +45 -52
- nautobot/core/templates/admin/base_site.html +0 -9
- nautobot/core/templates/admin/change_form.html +5 -5
- nautobot/core/templates/admin/change_list.html +8 -12
- nautobot/core/templates/admin/change_list_results.html +3 -3
- nautobot/core/templates/admin/config/config.html +24 -24
- nautobot/core/templates/admin/delete_confirmation.html +5 -5
- nautobot/core/templates/admin/edit_inline/stacked.html +5 -5
- nautobot/core/templates/admin/edit_inline/tabular.html +3 -3
- nautobot/core/templates/admin/includes/fieldset.html +15 -15
- nautobot/core/templates/admin/index.html +8 -8
- nautobot/core/templates/admin/submit_line.html +5 -5
- nautobot/core/templates/base_django.html +36 -32
- nautobot/core/templates/buttons/add.html +1 -1
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +2 -2
- nautobot/core/templates/buttons/export.html +17 -18
- nautobot/core/templates/buttons/job_import.html +2 -2
- nautobot/core/templates/components/breadcrumbs.html +19 -17
- nautobot/core/templates/components/button/dropdown.html +7 -5
- nautobot/core/templates/components/echarts.html +2 -0
- nautobot/core/templates/components/layout/one_over_two.html +3 -3
- nautobot/core/templates/components/layout/two_over_one.html +3 -3
- nautobot/core/templates/components/panel/body_content_data_table.html +2 -2
- nautobot/core/templates/components/panel/body_content_tags.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_generic.html +4 -2
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_table.html +4 -2
- nautobot/core/templates/components/panel/footer_contacts_table.html +4 -4
- nautobot/core/templates/components/panel/footer_content_table.html +3 -3
- nautobot/core/templates/components/panel/grouping_toggle.html +12 -11
- nautobot/core/templates/components/panel/header_extra_content_table.html +2 -11
- nautobot/core/templates/components/panel/panel.html +6 -3
- nautobot/core/templates/components/panel/stats_panel_body.html +9 -7
- nautobot/core/templates/components/tab/content_wrapper.html +29 -1
- nautobot/core/templates/components/tab/label_wrapper.html +10 -2
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +11 -4
- nautobot/core/templates/echarts/echarts.html +20 -0
- nautobot/core/templates/exceptions/import_error.html +2 -2
- nautobot/core/templates/exceptions/permission_error.html +1 -1
- nautobot/core/templates/exceptions/programming_error.html +2 -2
- nautobot/core/templates/generic/object_bulk_add_component.html +29 -20
- nautobot/core/templates/generic/object_bulk_create.html +87 -75
- nautobot/core/templates/generic/object_bulk_destroy.html +35 -37
- nautobot/core/templates/generic/object_bulk_remove.html +30 -26
- nautobot/core/templates/generic/object_bulk_rename.html +53 -40
- nautobot/core/templates/generic/object_bulk_update.html +36 -29
- nautobot/core/templates/generic/object_create.html +40 -27
- nautobot/core/templates/generic/object_import.html +36 -24
- nautobot/core/templates/generic/object_list.html +279 -215
- nautobot/core/templates/generic/object_notes.html +21 -11
- nautobot/core/templates/generic/object_retrieve.html +161 -213
- nautobot/core/templates/graphene/graphiql.html +113 -60
- nautobot/core/templates/home.html +164 -87
- nautobot/core/templates/import_success.html +3 -2
- nautobot/core/templates/inc/ajax_loader.html +1 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +25 -13
- nautobot/core/templates/inc/created_updated.html +12 -7
- nautobot/core/templates/inc/custom_fields/panel_data.html +28 -16
- nautobot/core/templates/inc/custom_fields_panel.html +3 -3
- nautobot/core/templates/inc/dynamic_groups_panel.html +3 -3
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +15 -15
- nautobot/core/templates/inc/footer.html +90 -40
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +75 -0
- nautobot/core/templates/inc/header_banners.html +17 -0
- nautobot/core/templates/inc/header_messages.html +6 -0
- nautobot/core/templates/inc/image_attachments.html +9 -9
- nautobot/core/templates/inc/javascript.html +7 -24
- nautobot/core/templates/inc/media.html +4 -29
- nautobot/core/templates/inc/modal.html +2 -2
- nautobot/core/templates/inc/nav_favorites.html +27 -0
- nautobot/core/templates/inc/nav_menu.html +150 -108
- nautobot/core/templates/inc/object_details_advanced_panel.html +84 -71
- nautobot/core/templates/inc/page_title.html +23 -0
- nautobot/core/templates/inc/paginator.html +39 -28
- nautobot/core/templates/inc/relationships/panel_override.html +3 -3
- nautobot/core/templates/inc/relationships_panel.html +3 -3
- nautobot/core/templates/inc/relationships_table_rows.html +1 -1
- nautobot/core/templates/inc/search_panel.html +22 -16
- nautobot/core/templates/inc/table.html +61 -36
- nautobot/core/templates/inc/tenancy_form_panel.html +3 -3
- nautobot/core/templates/login.html +17 -59
- nautobot/core/templates/modals/modal_theme.html +12 -23
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/panel_table.html +8 -12
- nautobot/core/templates/redoc_ui.html +80 -0
- nautobot/core/templates/rest_framework/api.html +43 -21
- nautobot/core/templates/search.html +12 -13
- nautobot/core/templates/swagger_ui.html +19 -4
- nautobot/core/templates/system_jobs/import_objects.html +70 -58
- nautobot/core/templates/template.css +0 -6
- nautobot/core/templates/utilities/comment_form.html +34 -0
- nautobot/core/templates/utilities/confirmation_form.html +17 -9
- nautobot/core/templates/utilities/obj_table.html +19 -11
- nautobot/core/templates/utilities/render_field.html +27 -21
- nautobot/core/templates/utilities/render_jinja2.html +22 -25
- nautobot/core/templates/utilities/templatetags/advanced_filter_indicator.html +8 -0
- nautobot/core/templates/utilities/templatetags/badge.html +1 -1
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +2 -3
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +482 -0
- nautobot/core/templates/utilities/templatetags/modal_form_as_dialog.html +14 -18
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +11 -11
- nautobot/core/templates/utilities/templatetags/table_config_form.html +51 -24
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/templatetags/utilization_graph.html +3 -3
- nautobot/core/templates/utilities/theme_preview.html +829 -566
- nautobot/core/templates/utilities/worker_status.html +42 -41
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templates/widgets/sluginput.html +2 -2
- nautobot/core/templatetags/buttons.py +38 -40
- nautobot/core/templatetags/helpers.py +105 -28
- nautobot/core/templatetags/ui_framework.py +17 -0
- nautobot/core/testing/api.py +76 -12
- nautobot/core/testing/filters.py +11 -27
- nautobot/core/testing/integration.py +128 -10
- nautobot/core/testing/mixins.py +7 -4
- nautobot/core/testing/utils.py +28 -5
- nautobot/core/testing/views.py +125 -27
- nautobot/core/tests/integration/test_app_home.py +39 -35
- nautobot/core/tests/integration/test_app_navbar.py +60 -67
- nautobot/core/tests/integration/test_filters.py +123 -55
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_home.py +10 -18
- nautobot/core/tests/integration/test_import_objects_ui.py +2 -9
- nautobot/core/tests/integration/test_navbar.py +41 -16
- nautobot/core/tests/integration/test_swagger.py +1 -7
- nautobot/core/tests/integration/test_theme.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +6 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_branching.py +154 -0
- nautobot/core/tests/test_breadcrumbs.py +7 -8
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_commands.py +0 -41
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_csv.py +4 -7
- nautobot/core/tests/test_filters.py +326 -318
- nautobot/core/tests/test_forms.py +19 -30
- nautobot/core/tests/test_graphql.py +67 -57
- nautobot/core/tests/test_models.py +1 -1
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +78 -10
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +61 -21
- nautobot/core/tests/test_templatetags_ui_framework.py +36 -18
- nautobot/core/tests/test_ui.py +207 -2
- nautobot/core/tests/test_utils.py +147 -2
- nautobot/core/tests/test_views.py +201 -64
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +190 -0
- nautobot/core/ui/constants.py +86 -0
- nautobot/core/ui/echarts.py +474 -0
- nautobot/core/ui/nav.py +5 -1
- nautobot/core/ui/object_detail.py +180 -16
- nautobot/core/urls.py +13 -1
- nautobot/core/utils/cache.py +71 -0
- nautobot/core/utils/data.py +8 -5
- nautobot/core/utils/filtering.py +8 -2
- nautobot/core/utils/git.py +3 -3
- nautobot/core/utils/lookup.py +87 -13
- nautobot/core/utils/migrations.py +22 -0
- nautobot/core/utils/module_loading.py +26 -0
- nautobot/core/utils/permissions.py +9 -5
- nautobot/core/views/__init__.py +114 -63
- nautobot/core/views/generic.py +34 -27
- nautobot/core/views/mixins.py +49 -27
- nautobot/core/views/renderers.py +3 -5
- nautobot/core/views/utils.py +10 -5
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/__init__.py +0 -0
- nautobot/data_validation/api/__init__.py +1 -0
- nautobot/data_validation/api/serializers.py +80 -0
- nautobot/data_validation/api/urls.py +20 -0
- nautobot/data_validation/api/views.py +44 -0
- nautobot/data_validation/apps.py +18 -0
- nautobot/data_validation/custom_validators.py +330 -0
- nautobot/data_validation/filters.py +133 -0
- nautobot/data_validation/form_mixin.py +25 -0
- nautobot/data_validation/forms.py +342 -0
- nautobot/data_validation/migrations/0001_initial.py +224 -0
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +324 -0
- nautobot/data_validation/migrations/__init__.py +0 -0
- nautobot/data_validation/models.py +361 -0
- nautobot/data_validation/navigation.py +74 -0
- nautobot/data_validation/signals.py +30 -0
- nautobot/data_validation/tables.py +259 -0
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +1 -0
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +11 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +20 -0
- nautobot/data_validation/tests/migrations/__init__.py +0 -0
- nautobot/data_validation/tests/migrations/test_migrations.py +489 -0
- nautobot/data_validation/tests/test_api.py +238 -0
- nautobot/data_validation/tests/test_custom_validators.py +423 -0
- nautobot/data_validation/tests/test_data_compliance_rules.py +85 -0
- nautobot/data_validation/tests/test_filters.py +240 -0
- nautobot/data_validation/tests/test_form_mixin.py +115 -0
- nautobot/data_validation/tests/test_models.py +393 -0
- nautobot/data_validation/tests/test_views.py +435 -0
- nautobot/data_validation/urls.py +21 -0
- nautobot/data_validation/views.py +227 -0
- nautobot/dcim/api/serializers.py +10 -13
- nautobot/dcim/api/urls.py +2 -0
- nautobot/dcim/api/views.py +7 -0
- 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} +70 -157
- nautobot/dcim/forms.py +12 -6
- nautobot/dcim/graphql/types.py +1 -0
- nautobot/dcim/migrations/0075_add_deviceclusterassignment.py +52 -0
- nautobot/dcim/migrations/0076_device_cluster_to_clusters_data_migration.py +40 -0
- nautobot/dcim/migrations/0077_remove_device_cluster.py +14 -0
- 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/__init__.py +2 -0
- nautobot/dcim/models/device_components.py +3 -1
- nautobot/dcim/models/devices.py +115 -51
- nautobot/dcim/navigation.py +7 -3
- nautobot/dcim/querysets.py +6 -0
- nautobot/dcim/signals.py +19 -0
- nautobot/dcim/tables/devices.py +9 -5
- nautobot/dcim/tables/template_code.py +191 -102
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_connect.html +62 -146
- nautobot/dcim/templates/dcim/cable_retrieve.html +10 -10
- nautobot/dcim/templates/dcim/cable_trace.html +15 -17
- nautobot/dcim/templates/dcim/console_port_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/consoleport.html +18 -17
- nautobot/dcim/templates/dcim/consoleserverport.html +18 -17
- nautobot/dcim/templates/dcim/controller_create.html +12 -8
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +1 -1
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +6 -6
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/device/config.html +17 -19
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +4 -4
- nautobot/dcim/templates/dcim/device/status.html +20 -20
- nautobot/dcim/templates/dcim/device_component_add.html +24 -15
- nautobot/dcim/templates/dcim/device_create.html +120 -120
- nautobot/dcim/templates/dcim/device_list.html +75 -12
- nautobot/dcim/templates/dcim/devicebay.html +7 -7
- nautobot/dcim/templates/dcim/devicebay_populate.html +29 -23
- nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +6 -6
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/devicetype_component_add.html +25 -19
- nautobot/dcim/templates/dcim/devicetype_list.html +4 -4
- nautobot/dcim/templates/dcim/devicetype_update.html +9 -9
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +3 -3
- nautobot/dcim/templates/dcim/frontport.html +21 -20
- nautobot/dcim/templates/dcim/inc/cable_form.html +7 -7
- nautobot/dcim/templates/dcim/inc/cable_termination.html +1 -1
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +18 -9
- nautobot/dcim/templates/dcim/inc/detail_softwareversion_softwareimagefile_rows.html +1 -1
- nautobot/dcim/templates/dcim/inc/device_interface_filter.html +1 -1
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/homepage_connections.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +42 -22
- nautobot/dcim/templates/dcim/interface_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/interface_edit.html +26 -11
- nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem_add.html +21 -10
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +6 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +24 -18
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/location_update.html +9 -9
- nautobot/dcim/templates/dcim/locationtype.html +0 -1
- nautobot/dcim/templates/dcim/module/base.html +67 -27
- nautobot/dcim/templates/dcim/module_consoleports.html +13 -15
- nautobot/dcim/templates/dcim/module_consoleserverports.html +13 -15
- nautobot/dcim/templates/dcim/module_frontports.html +13 -15
- nautobot/dcim/templates/dcim/module_interfaces.html +14 -16
- nautobot/dcim/templates/dcim/module_list.html +59 -10
- nautobot/dcim/templates/dcim/module_modulebays.html +12 -14
- nautobot/dcim/templates/dcim/module_poweroutlets.html +13 -15
- nautobot/dcim/templates/dcim/module_powerports.html +13 -15
- nautobot/dcim/templates/dcim/module_rearports.html +13 -15
- nautobot/dcim/templates/dcim/module_retrieve.html +3 -3
- nautobot/dcim/templates/dcim/module_update.html +15 -9
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -93
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +7 -7
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +74 -35
- nautobot/dcim/templates/dcim/platform_create.html +9 -9
- nautobot/dcim/templates/dcim/power_port_connection_list.html +3 -3
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed_edit.html +15 -15
- nautobot/dcim/templates/dcim/poweroutlet.html +13 -13
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +17 -16
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/rack_elevation_list.html +21 -9
- nautobot/dcim/templates/dcim/rack_retrieve.html +75 -57
- nautobot/dcim/templates/dcim/rack_update.html +14 -14
- nautobot/dcim/templates/dcim/rackreservation.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation_edit.html +6 -6
- nautobot/dcim/templates/dcim/rearport.html +19 -18
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/trace/circuit.html +1 -1
- nautobot/dcim/templates/dcim/trace/device.html +1 -1
- nautobot/dcim/templates/dcim/trace/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/trace/termination.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +25 -16
- nautobot/dcim/templates/dcim/virtualchassis_create.html +6 -6
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_update.html +36 -22
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +9 -9
- nautobot/dcim/tests/integration/test_controller.py +6 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +7 -7
- nautobot/dcim/tests/integration/test_create_device.py +9 -9
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +7 -2
- nautobot/dcim/tests/integration/test_fileinputpicker.py +5 -7
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +2 -0
- nautobot/dcim/tests/integration/test_module_bay_position.py +4 -1
- nautobot/dcim/tests/test_api.py +86 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +159 -110
- nautobot/dcim/tests/test_graphql.py +32 -36
- nautobot/dcim/tests/test_jobs.py +1 -1
- nautobot/dcim/tests/test_models.py +229 -1
- nautobot/dcim/tests/test_views.py +31 -20
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +77 -41
- nautobot/extras/api/serializers.py +83 -19
- nautobot/extras/api/urls.py +7 -0
- nautobot/extras/api/views.py +243 -140
- nautobot/extras/choices.py +34 -13
- nautobot/extras/constants.py +1 -1
- nautobot/extras/context_managers.py +26 -26
- nautobot/extras/datasources/git.py +22 -0
- nautobot/extras/datasources/registry.py +3 -0
- nautobot/extras/exceptions.py +5 -0
- nautobot/extras/factory.py +11 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +4 -3
- nautobot/extras/{filters/__init__.py → filters.py} +203 -58
- nautobot/extras/forms/base.py +2 -1
- nautobot/extras/forms/forms.py +225 -20
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/homepage.py +21 -2
- nautobot/extras/jobs.py +2 -8
- nautobot/extras/jobs_ui.py +2 -2
- nautobot/extras/management/__init__.py +9 -0
- nautobot/extras/managers.py +31 -22
- nautobot/extras/migrations/0126_approval_workflow_pre_check.py +58 -0
- nautobot/extras/migrations/0127_approval_workflow_models.py +266 -0
- nautobot/extras/migrations/0128_remove_job_approval_required_and_more.py +29 -0
- 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 +14 -3
- nautobot/extras/models/approvals.py +556 -0
- nautobot/extras/models/change_logging.py +1 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/customfields.py +57 -22
- nautobot/extras/models/datasources.py +21 -0
- nautobot/extras/models/groups.py +2 -0
- nautobot/extras/models/jobs.py +122 -39
- nautobot/extras/models/metadata.py +2 -3
- nautobot/extras/models/mixins.py +129 -1
- nautobot/extras/models/models.py +22 -14
- nautobot/extras/models/relationships.py +47 -10
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/models/tags.py +1 -1
- nautobot/extras/navigation.py +42 -15
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +1 -23
- nautobot/extras/plugins/tables.py +8 -6
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/validators.py +10 -10
- nautobot/extras/plugins/views.py +1 -5
- nautobot/extras/querysets.py +17 -21
- nautobot/extras/signals.py +23 -8
- nautobot/extras/tables.py +420 -99
- nautobot/extras/templates/extras/approval_dashboard.html +15 -0
- nautobot/extras/templates/extras/approval_workflow/approve.html +11 -0
- nautobot/extras/templates/extras/approval_workflow/comment.html +9 -0
- nautobot/extras/templates/extras/approval_workflow/deny.html +10 -0
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +77 -0
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +29 -0
- nautobot/extras/templates/extras/configcontext_update.html +12 -12
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +9 -9
- nautobot/extras/templates/extras/configcontextschema_update.html +6 -6
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield_update.html +12 -12
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_edit.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +17 -17
- nautobot/extras/templates/extras/dynamicgroup_update.html +24 -24
- nautobot/extras/templates/extras/externalintegration_update.html +6 -6
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/gitrepository_object_edit.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +1 -1
- nautobot/extras/templates/extras/gitrepository_retrieve.html +12 -12
- nautobot/extras/templates/extras/gitrepository_update.html +25 -7
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -1
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +38 -0
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +14 -13
- nautobot/extras/templates/extras/inc/configcontext_format.html +11 -4
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +7 -7
- nautobot/extras/templates/extras/inc/job_label.html +5 -5
- nautobot/extras/templates/extras/inc/job_table.html +23 -10
- nautobot/extras/templates/extras/inc/job_tiles.html +33 -21
- nautobot/extras/templates/extras/inc/jobresult.html +6 -6
- nautobot/extras/templates/extras/inc/json_format.html +11 -4
- nautobot/extras/templates/extras/inc/object_contact_header.html +6 -6
- nautobot/extras/templates/extras/inc/overridable_field.html +16 -15
- nautobot/extras/templates/extras/inc/panel_approvalworkflowstage.html +34 -0
- nautobot/extras/templates/extras/inc/panel_changelog.html +9 -9
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +8 -6
- nautobot/extras/templates/extras/inc/tags_panel.html +3 -3
- nautobot/extras/templates/extras/job.html +154 -155
- nautobot/extras/templates/extras/job_approval_confirmation.html +4 -27
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -1
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/job_edit.html +69 -64
- nautobot/extras/templates/extras/job_list.html +37 -60
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/jobresult_retrieve.html +17 -17
- nautobot/extras/templates/extras/marketplace.html +62 -71
- nautobot/extras/templates/extras/metadatatype_create.html +9 -9
- nautobot/extras/templates/extras/note.html +1 -1
- nautobot/extras/templates/extras/object_approvalworkflow.html +36 -0
- nautobot/extras/templates/extras/object_assign_contact_or_team.html +16 -7
- nautobot/extras/templates/extras/object_configcontext.html +20 -20
- nautobot/extras/templates/extras/object_new_contact.html +6 -6
- nautobot/extras/templates/extras/object_new_team.html +6 -6
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/objectchange_retrieve.html +37 -56
- nautobot/extras/templates/extras/plugin_detail.html +40 -41
- nautobot/extras/templates/extras/plugins_list.html +23 -38
- nautobot/extras/templates/extras/plugins_tiles.html +28 -28
- nautobot/extras/templates/extras/role_retrieve.html +112 -48
- nautobot/extras/templates/extras/scheduledjob.html +25 -28
- nautobot/extras/templates/extras/secret_create.html +11 -11
- nautobot/extras/templates/extras/secretsgroup_update.html +6 -6
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +3 -3
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +3 -3
- nautobot/extras/templates/extras/templatetags/log_level.html +1 -1
- nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -2
- nautobot/extras/templates/extras/webhook.html +12 -12
- nautobot/extras/templatetags/approvals.py +19 -0
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_computedfields.py +5 -8
- nautobot/extras/tests/integration/test_configcontextschema.py +43 -48
- nautobot/extras/tests/integration/test_customfields.py +33 -33
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -10
- nautobot/extras/tests/integration/test_jobs.py +2 -4
- nautobot/extras/tests/integration/test_notes.py +3 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +35 -27
- nautobot/extras/tests/integration/test_relationships.py +7 -11
- nautobot/extras/tests/integration/test_tagfilter.py +3 -11
- nautobot/extras/tests/test_api.py +786 -242
- nautobot/extras/tests/test_approvals.py +715 -0
- nautobot/extras/tests/test_changelog.py +18 -14
- nautobot/extras/tests/test_customfields.py +14 -13
- nautobot/extras/tests/test_datasources.py +1 -1
- nautobot/extras/tests/test_dynamicgroups.py +9 -4
- nautobot/extras/tests/test_filters.py +443 -13
- nautobot/extras/tests/test_forms.py +18 -57
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +81 -1
- nautobot/extras/tests/test_models.py +378 -47
- nautobot/extras/tests/test_plugins.py +47 -13
- nautobot/extras/tests/test_relationships.py +7 -2
- nautobot/extras/tests/test_utils.py +2 -0
- nautobot/extras/tests/test_views.py +780 -493
- nautobot/extras/urls.py +36 -12
- nautobot/extras/utils.py +58 -12
- nautobot/extras/views.py +668 -209
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +35 -71
- 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 -1
- nautobot/ipam/querysets.py +1 -2
- nautobot/ipam/tables.py +26 -17
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +6 -6
- nautobot/ipam/templates/ipam/inc/service.html +8 -8
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +3 -2
- nautobot/ipam/templates/ipam/ipaddress.html +27 -13
- nautobot/ipam/templates/ipam/ipaddress_assign.html +31 -24
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +3 -3
- nautobot/ipam/templates/ipam/ipaddress_edit.html +9 -9
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddress_merge.html +195 -186
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +7 -5
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_create.html +9 -9
- nautobot/ipam/templates/ipam/prefix_list.html +15 -14
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_update.html +6 -6
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/templates/ipam/vrf_edit.html +15 -15
- nautobot/ipam/tests/integration/test_prefixes.py +5 -13
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +20 -7
- nautobot/ipam/tests/test_filters.py +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +24 -21
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +49 -7
- nautobot/project-static/dist/css/graphql-libraries.css +655 -0
- nautobot/project-static/dist/css/graphql-libraries.css.map +1 -0
- nautobot/project-static/dist/css/materialdesignicons.css +3 -0
- nautobot/project-static/dist/css/materialdesignicons.css.map +1 -0
- nautobot/project-static/dist/css/nautobot.css +13 -0
- nautobot/project-static/dist/css/nautobot.css.map +1 -0
- nautobot/project-static/dist/js/graphql-libraries.js +3 -0
- nautobot/project-static/dist/js/graphql-libraries.js.LICENSE.txt +62 -0
- nautobot/project-static/dist/js/graphql-libraries.js.map +1 -0
- nautobot/project-static/dist/js/libraries.js +3 -0
- nautobot/project-static/dist/js/libraries.js.LICENSE.txt +65 -0
- nautobot/project-static/dist/js/libraries.js.map +1 -0
- nautobot/project-static/dist/js/materialdesignicons.js +0 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js +2 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -0
- nautobot/project-static/dist/js/nautobot.js +2 -0
- nautobot/project-static/dist/js/nautobot.js.map +1 -0
- nautobot/project-static/fonts/Montserrat-v30-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Light.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Regular.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Light.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Regular.woff2 +0 -0
- nautobot/project-static/img/jinja_logo.svg +21 -92
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/editor.js +4 -4
- nautobot/project-static/js/forms.js +67 -717
- nautobot/project-static/js/job_result.js +2 -2
- nautobot/project-static/nautobot-icons/360-degrees.svg +3 -0
- nautobot/project-static/nautobot-icons/arrow-decision.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-expand-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-2-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/atom.svg +3 -0
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/branch.svg +3 -0
- nautobot/project-static/nautobot-icons/briefcase-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data.svg +3 -0
- nautobot/project-static/nautobot-icons/cast.svg +3 -0
- nautobot/project-static/nautobot-icons/check-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-check.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-upload.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +3 -0
- nautobot/project-static/nautobot-icons/compass.svg +3 -0
- nautobot/project-static/nautobot-icons/control-panel.svg +3 -0
- nautobot/project-static/nautobot-icons/credit-card.svg +3 -0
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +3 -0
- nautobot/project-static/nautobot-icons/direction.svg +3 -0
- nautobot/project-static/nautobot-icons/elements.svg +3 -0
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/globe-2.svg +3 -0
- nautobot/project-static/nautobot-icons/globe.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +3 -0
- nautobot/project-static/nautobot-icons/history.svg +3 -0
- nautobot/project-static/nautobot-icons/ip.svg +3 -0
- nautobot/project-static/nautobot-icons/laptop.svg +3 -0
- nautobot/project-static/nautobot-icons/lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/list-unordered.svg +3 -0
- nautobot/project-static/nautobot-icons/map-view.svg +3 -0
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-2.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-3.svg +3 -0
- nautobot/project-static/nautobot-icons/plug.svg +3 -0
- nautobot/project-static/nautobot-icons/refresh-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/rocket-2.svg +3 -0
- nautobot/project-static/nautobot-icons/rotate-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/route.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +3 -0
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server-2.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +3 -0
- nautobot/project-static/nautobot-icons/share.svg +3 -0
- nautobot/project-static/nautobot-icons/shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/sitemap-outline.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert-2.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert.svg +3 -0
- nautobot/project-static/nautobot-icons/star-filled.svg +3 -0
- nautobot/project-static/nautobot-icons/star.svg +3 -0
- nautobot/project-static/nautobot-icons/transform.svg +3 -0
- nautobot/project-static/nautobot-icons/wifi.svg +3 -0
- 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/templates/tenancy/tenant_create.html +6 -6
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/.gitignore +137 -0
- nautobot/ui/.node-version +1 -0
- nautobot/ui/.prettierignore +3 -0
- nautobot/ui/eslint.config.js +33 -0
- nautobot/ui/package-lock.json +6594 -0
- nautobot/ui/package.json +67 -0
- nautobot/ui/prettier.config.js +9 -0
- nautobot/ui/src/js/collapse.js +69 -0
- nautobot/ui/src/js/cookie.js +31 -0
- nautobot/ui/src/js/draggable.js +101 -0
- nautobot/ui/src/js/drawer.js +106 -0
- nautobot/ui/src/js/form.js +23 -0
- nautobot/ui/src/js/history.js +51 -0
- nautobot/ui/src/js/nautobot-graphiql.js +19 -0
- nautobot/ui/src/js/nautobot.js +128 -0
- nautobot/ui/src/js/search.js +274 -0
- nautobot/ui/src/js/select2.js +318 -0
- nautobot/ui/src/js/sidenav.js +87 -0
- nautobot/ui/src/js/tabs.js +139 -0
- nautobot/ui/src/js/theme.js +104 -0
- nautobot/ui/src/js/utils.js +54 -0
- nautobot/ui/src/scss/colors.scss +58 -0
- nautobot/ui/src/scss/nautobot.scss +2471 -0
- nautobot/ui/webpack.config.js +148 -0
- nautobot/users/apps.py +3 -0
- nautobot/users/filters.py +7 -11
- nautobot/users/forms.py +10 -0
- nautobot/users/models.py +8 -0
- nautobot/users/templates/users/advanced_settings_edit.html +31 -21
- nautobot/users/templates/users/api_tokens.html +61 -51
- nautobot/users/templates/users/base.html +23 -31
- nautobot/users/templates/users/change_password.html +29 -19
- nautobot/users/templates/users/preferences.html +55 -45
- nautobot/users/templates/users/profile.html +45 -14
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/urls.py +2 -0
- nautobot/users/views.py +70 -2
- nautobot/virtualization/api/views.py +1 -1
- nautobot/virtualization/filters.py +18 -32
- nautobot/virtualization/forms.py +22 -59
- nautobot/virtualization/models.py +1 -19
- nautobot/virtualization/navigation.py +3 -1
- nautobot/virtualization/tables.py +10 -6
- nautobot/virtualization/templates/virtualization/cluster.html +13 -13
- nautobot/virtualization/templates/virtualization/cluster_edit.html +6 -6
- nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +24 -16
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +4 -4
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +27 -25
- nautobot/virtualization/templates/virtualization/vminterface.html +5 -5
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +27 -11
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_models.py +20 -5
- nautobot/virtualization/tests/test_views.py +3 -5
- nautobot/virtualization/urls.py +0 -11
- nautobot/virtualization/views.py +5 -122
- 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/filters.py +0 -8
- nautobot/wireless/navigation.py +3 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +6 -6
- nautobot/wireless/tests/integration/test_radio_profile.py +3 -7
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +5 -4
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +802 -707
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/core/management/commands/check_job_approval_status.py +0 -47
- nautobot/core/templates/search_form.html +0 -9
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +0 -87
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/extras/templates/extras/job_approval_request.html +0 -134
- nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +0 -28
- nautobot/ipam/mixins.py +0 -32
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +0 -587
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +0 -6865
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +0 -288
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.js +0 -2580
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/js/npm.js +0 -13
- nautobot/project-static/clipboard.js-2.0.9/clipboard.min.js +0 -7
- nautobot/project-static/css/base.css +0 -1040
- nautobot/project-static/css/dark.css +0 -282
- nautobot/project-static/flatpickr-4.6.9/flatpickr.min.js +0 -2
- nautobot/project-static/flatpickr-4.6.9/themes/light.min.css +0 -1
- nautobot/project-static/graphiql-1.5.16/graphiql.min.css +0 -12
- nautobot/project-static/graphiql-1.5.16/graphiql.min.js +0 -11
- nautobot/project-static/highlight.js-11.9.0/github-dark.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/github.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/highlight.min.js +0 -378
- nautobot/project-static/jquery/jquery-3.7.1.min.js +0 -2
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_444444_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_555555_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777620_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777777_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_cc0000_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_ffffff_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +0 -7
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +0 -6
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +0 -5
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.theme.min.css +0 -5
- nautobot/project-static/js/homepage_layout.js +0 -182
- nautobot/project-static/js/nav_menu.js +0 -250
- nautobot/project-static/js/theme.js +0 -133
- nautobot/project-static/materialdesignicons-7.4.47/LICENSE +0 -20
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/react-16.14.0/react.production.min.js +0 -32
- nautobot/project-static/react-dom-16.14.0/react-dom.production.min.js +0 -239
- nautobot/project-static/select2-4.0.13/i18n/af.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ar.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/az.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bg.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bn.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ca.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/cs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/da.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/de.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/dsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/el.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/en.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/es.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/et.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/eu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fa.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/gl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/he.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hy.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/id.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/is.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/it.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ja.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ka.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/km.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ko.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/mk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ms.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ne.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ps.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt-BR.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ro.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ru.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sq.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr-Cyrl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/th.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/uk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/vi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-CN.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-TW.js +0 -3
- nautobot/project-static/select2-4.0.13/select2.min.css +0 -1
- nautobot/project-static/select2-4.0.13/select2.min.js +0 -2
- nautobot/project-static/select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css +0 -7
- nautobot/project-static/subscriptions-transport-ws-0.9.18/client.min.js +0 -8
- nautobot/project-static/whatwg-fetch-3.6.2/fetch.umd.min.js +0 -8
- nautobot/virtualization/templates/virtualization/cluster_add_devices.html +0 -37
- /nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.ttf → dist/1fcc36272ea3e53d0031.ttf} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.eot → dist/2146c3c82b553977abc7.eot} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff → dist/e55a20c80650829ec5fd.woff} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 → dist/ec024da790d2972da002.woff2} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
{% load form_helpers %}
|
|
3
3
|
|
|
4
4
|
{% block form %}
|
|
5
|
-
<div class="
|
|
6
|
-
<div class="
|
|
7
|
-
<div class="
|
|
5
|
+
<div class="card">
|
|
6
|
+
<div class="card-header"><strong>VLAN</strong></div>
|
|
7
|
+
<div class="card-body">
|
|
8
8
|
{% render_field form.vid %}
|
|
9
9
|
{% render_field form.name %}
|
|
10
10
|
{% render_field form.status %}
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
{% render_field form.description %}
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
|
-
<div class="
|
|
16
|
-
<div class="
|
|
17
|
-
<div class="
|
|
15
|
+
<div class="card">
|
|
16
|
+
<div class="card-header"><strong>Assignment</strong></div>
|
|
17
|
+
<div class="card-body">
|
|
18
18
|
{% render_field form.locations %}
|
|
19
19
|
{% render_field form.vlan_group %}
|
|
20
20
|
</div>
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
{% load form_helpers %}
|
|
3
3
|
|
|
4
4
|
{% block form %}
|
|
5
|
-
<div class="
|
|
6
|
-
<div class="
|
|
7
|
-
<div class="
|
|
5
|
+
<div class="card">
|
|
6
|
+
<div class="card-header"><strong>VRF</strong></div>
|
|
7
|
+
<div class="card-body">
|
|
8
8
|
{% render_field form.name %}
|
|
9
9
|
{% render_field form.namespace %}
|
|
10
10
|
{% render_field form.rd %}
|
|
@@ -12,27 +12,27 @@
|
|
|
12
12
|
{% render_field form.description %}
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
|
-
<div class="
|
|
16
|
-
<div class="
|
|
17
|
-
<div class="
|
|
15
|
+
<div class="card">
|
|
16
|
+
<div class="card-header"><strong>Devices</strong></div>
|
|
17
|
+
<div class="card-body">
|
|
18
18
|
{% render_field form.devices %}
|
|
19
19
|
</div>
|
|
20
20
|
</div>
|
|
21
|
-
<div class="
|
|
22
|
-
<div class="
|
|
23
|
-
<div class="
|
|
21
|
+
<div class="card">
|
|
22
|
+
<div class="card-header"><strong>Virtual Machines</strong></div>
|
|
23
|
+
<div class="card-body">
|
|
24
24
|
{% render_field form.virtual_machines %}
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
-
<div class="
|
|
28
|
-
<div class="
|
|
29
|
-
<div class="
|
|
27
|
+
<div class="card">
|
|
28
|
+
<div class="card-header"><strong>Prefixes</strong></div>
|
|
29
|
+
<div class="card-body">
|
|
30
30
|
{% render_field form.prefixes %}
|
|
31
31
|
</div>
|
|
32
32
|
</div>
|
|
33
|
-
<div class="
|
|
34
|
-
<div class="
|
|
35
|
-
<div class="
|
|
33
|
+
<div class="card">
|
|
34
|
+
<div class="card-header"><strong>Route Targets</strong></div>
|
|
35
|
+
<div class="card-body">
|
|
36
36
|
{% render_field form.import_targets %}
|
|
37
37
|
{% render_field form.export_targets %}
|
|
38
38
|
</div>
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import netaddr
|
|
2
2
|
|
|
3
|
-
from nautobot.core.testing.integration import SeleniumTestCase
|
|
3
|
+
from nautobot.core.testing.integration import ObjectDetailsMixin, SeleniumTestCase
|
|
4
4
|
from nautobot.extras.models import Status
|
|
5
5
|
from nautobot.ipam.choices import PrefixTypeChoices
|
|
6
6
|
from nautobot.ipam.models import Namespace, Prefix
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class PrefixHierarchyTest(SeleniumTestCase):
|
|
9
|
+
class PrefixHierarchyTest(SeleniumTestCase, ObjectDetailsMixin):
|
|
10
10
|
"""
|
|
11
11
|
This test case tests the indented Prefix hierarchy displayed in the Prefix list view.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
def setUp(self):
|
|
15
15
|
super().setUp()
|
|
16
|
-
|
|
17
|
-
self.user.is_superuser = True
|
|
18
|
-
self.user.save()
|
|
19
|
-
self.login(self.user.username, self.password)
|
|
20
|
-
|
|
21
|
-
def tearDown(self):
|
|
22
|
-
self.logout()
|
|
23
|
-
super().tearDown()
|
|
16
|
+
self.login_as_superuser()
|
|
24
17
|
|
|
25
18
|
def test_child_relationship_visible(self):
|
|
26
19
|
"""
|
|
@@ -43,10 +36,9 @@ class PrefixHierarchyTest(SeleniumTestCase):
|
|
|
43
36
|
|
|
44
37
|
# Navigate to Namespace Prefixes list view
|
|
45
38
|
self.browser.visit(self.live_server_url)
|
|
46
|
-
self.
|
|
47
|
-
self.browser.links.find_by_partial_text("Namespaces").click()
|
|
39
|
+
self.click_navbar_entry("IPAM", "Namespaces")
|
|
48
40
|
self.browser.links.find_by_text(namespace.name).click()
|
|
49
|
-
self.
|
|
41
|
+
self.switch_tab("Prefixes")
|
|
50
42
|
|
|
51
43
|
self.assertEqual(len(self.browser.find_by_tag("tr")[1].find_by_text("10.0.0.0/16")), 1) # 10.0.0.0/16 is first
|
|
52
44
|
self.assertEqual(len(self.browser.find_by_tag("tr")[2].find_by_text("10.0.0.0/24")), 1) # 10.0.0.0/24 is second
|
|
@@ -508,3 +508,92 @@ class IPAMDataMigration0031TestCase(MigratorTestCase):
|
|
|
508
508
|
for ip in IPAddress.objects.iterator():
|
|
509
509
|
self.assertLessEqual(netaddr.IPAddress(ip.parent.network), netaddr.IPAddress(ip.host))
|
|
510
510
|
self.assertGreaterEqual(netaddr.IPAddress(ip.parent.broadcast), netaddr.IPAddress(ip.host))
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class NamespaceTenantCircularDependencyTestCase(MigratorTestCase):
|
|
514
|
+
"""Test that our 3-phase migration approach resolves circular dependencies and supports rollback."""
|
|
515
|
+
|
|
516
|
+
migrate_from = ("ipam", "0029_ip_address_to_interface_uniqueness_constraints") # Before namespace creation
|
|
517
|
+
migrate_to = ("ipam", "0054_namespace_tenant") # After our complete sequence
|
|
518
|
+
|
|
519
|
+
def prepare(self):
|
|
520
|
+
"""Create minimal data to test namespace-tenant migration sequence."""
|
|
521
|
+
# Create basic tenancy data in old state
|
|
522
|
+
TenantGroup = self.old_state.apps.get_model("tenancy", "TenantGroup")
|
|
523
|
+
Tenant = self.old_state.apps.get_model("tenancy", "Tenant")
|
|
524
|
+
|
|
525
|
+
self.tenant_group = TenantGroup.objects.create(name="Test Group")
|
|
526
|
+
self.tenant1 = Tenant.objects.create(name="Test Tenant 1", tenant_group=self.tenant_group)
|
|
527
|
+
self.tenant2 = Tenant.objects.create(name="Test Tenant 2", tenant_group=self.tenant_group)
|
|
528
|
+
|
|
529
|
+
def test_forward_migration_resolves_circular_dependency(self):
|
|
530
|
+
"""Test that fresh database creation works with tenant on Namespace."""
|
|
531
|
+
Namespace = self.new_state.apps.get_model("ipam", "Namespace")
|
|
532
|
+
VRF = self.new_state.apps.get_model("ipam", "VRF")
|
|
533
|
+
Prefix = self.new_state.apps.get_model("ipam", "Prefix")
|
|
534
|
+
|
|
535
|
+
# Verify Global namespace exists
|
|
536
|
+
global_namespace = Namespace.objects.get(name="Global")
|
|
537
|
+
self.assertIsNotNone(global_namespace)
|
|
538
|
+
|
|
539
|
+
# Verify tenant field exists on Namespace
|
|
540
|
+
self.assertTrue(hasattr(global_namespace, "tenant"))
|
|
541
|
+
|
|
542
|
+
# Verify VRF and Prefix have namespace defaults working
|
|
543
|
+
vrf_field = VRF._meta.get_field("namespace")
|
|
544
|
+
prefix_field = Prefix._meta.get_field("namespace")
|
|
545
|
+
self.assertIsNotNone(vrf_field.default)
|
|
546
|
+
self.assertIsNotNone(prefix_field.default)
|
|
547
|
+
|
|
548
|
+
def test_namespace_tenant_field_added(self):
|
|
549
|
+
"""Test that tenant field was properly added to Namespace model."""
|
|
550
|
+
Namespace = self.new_state.apps.get_model("ipam", "Namespace")
|
|
551
|
+
|
|
552
|
+
# Create a namespace with tenant assignment
|
|
553
|
+
test_namespace = Namespace.objects.create(
|
|
554
|
+
name="Test Namespace", tenant_id=self.tenant1.pk, description="Test namespace with tenant"
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Verify tenant field exists and works
|
|
558
|
+
self.assertEqual(test_namespace.tenant_id, self.tenant1.pk)
|
|
559
|
+
self.assertEqual(test_namespace.name, "Test Namespace")
|
|
560
|
+
|
|
561
|
+
# Verify tenant relationship works
|
|
562
|
+
retrieved_namespace = Namespace.objects.get(name="Test Namespace")
|
|
563
|
+
self.assertEqual(retrieved_namespace.tenant_id, self.tenant1.pk)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class NamespaceTenantRollbackTestCase(MigratorTestCase):
|
|
567
|
+
"""Test rolling back the namespace-tenant feature works safely."""
|
|
568
|
+
|
|
569
|
+
# For rollback testing: migrate_from is AFTER our changes, migrate_to is BEFORE
|
|
570
|
+
migrate_from = ("ipam", "0054_namespace_tenant") # After our complete sequence
|
|
571
|
+
migrate_to = ("ipam", "0052_alter_ipaddress_index_together_and_more") # Before our changes
|
|
572
|
+
|
|
573
|
+
def prepare(self):
|
|
574
|
+
"""Create namespace data in the 'after' state to test rollback."""
|
|
575
|
+
# Create tenancy data first
|
|
576
|
+
TenantGroup = self.old_state.apps.get_model("tenancy", "TenantGroup")
|
|
577
|
+
Tenant = self.old_state.apps.get_model("tenancy", "Tenant")
|
|
578
|
+
|
|
579
|
+
self.tenant_group = TenantGroup.objects.create(name="Test Tenant Group")
|
|
580
|
+
self.tenant1 = Tenant.objects.create(name="Test Tenant 1", tenant_group=self.tenant_group)
|
|
581
|
+
|
|
582
|
+
# Create namespace with tenant assignment in the 'after' state
|
|
583
|
+
Namespace = self.old_state.apps.get_model("ipam", "Namespace")
|
|
584
|
+
self.test_namespace = Namespace.objects.create(
|
|
585
|
+
name="Test Namespace", tenant_id=self.tenant1.pk, description="Test namespace for rollback"
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
def test_rollback_migration_preserves_data_integrity(self):
|
|
589
|
+
"""Test that rolling back preserves namespace data but removes tenant field."""
|
|
590
|
+
# In the rolled-back state, tenant field should be gone
|
|
591
|
+
Namespace = self.new_state.apps.get_model("ipam", "Namespace")
|
|
592
|
+
rolled_back_namespace = Namespace.objects.get(pk=self.test_namespace.pk)
|
|
593
|
+
|
|
594
|
+
# Verify core fields preserved
|
|
595
|
+
self.assertEqual(rolled_back_namespace.name, "Test Namespace")
|
|
596
|
+
self.assertEqual(rolled_back_namespace.description, "Test namespace for rollback")
|
|
597
|
+
|
|
598
|
+
# Verify tenant field no longer exists (rolled back)
|
|
599
|
+
self.assertFalse(hasattr(rolled_back_namespace, "tenant"))
|
nautobot/ipam/tests/test_api.py
CHANGED
|
@@ -40,6 +40,7 @@ from nautobot.ipam.models import (
|
|
|
40
40
|
VRFDeviceAssignment,
|
|
41
41
|
VRFPrefixAssignment,
|
|
42
42
|
)
|
|
43
|
+
from nautobot.tenancy.models import Tenant
|
|
43
44
|
from nautobot.virtualization.models import Cluster, ClusterType, VirtualMachine, VMInterface
|
|
44
45
|
|
|
45
46
|
|
|
@@ -57,22 +58,28 @@ class NamespaceTest(APIViewTestCases.APIViewTestCase):
|
|
|
57
58
|
@classmethod
|
|
58
59
|
def setUpTestData(cls):
|
|
59
60
|
location = Location.objects.first()
|
|
61
|
+
tenant = Tenant.objects.first()
|
|
60
62
|
cls.create_data = [
|
|
61
63
|
{
|
|
62
64
|
"name": "Purple Monkey Namespace 1",
|
|
63
|
-
"description": "A
|
|
65
|
+
"description": "A namespace with a tenant and location.",
|
|
64
66
|
"location": location.pk,
|
|
67
|
+
"tenant": tenant.pk,
|
|
65
68
|
},
|
|
66
69
|
{
|
|
67
70
|
"name": "Purple Monkey Namespace 2",
|
|
68
|
-
"description": "A
|
|
69
|
-
"
|
|
71
|
+
"description": "A namespace with a tenant and no location.",
|
|
72
|
+
"tenant": tenant.pk,
|
|
70
73
|
},
|
|
71
74
|
{
|
|
72
75
|
"name": "Purple Monkey Namespace 3",
|
|
73
|
-
"description": "A
|
|
76
|
+
"description": "A namespace with no tenant but with a location.",
|
|
74
77
|
"location": location.pk,
|
|
75
78
|
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Purple Monkey Namespace 4",
|
|
81
|
+
"description": "A namespace with no tenant and no location.",
|
|
82
|
+
},
|
|
76
83
|
]
|
|
77
84
|
cls.bulk_update_data = {
|
|
78
85
|
"description": "A perfectly new description.",
|
|
@@ -124,7 +131,13 @@ class VRFDeviceAssignmentTest(APIViewTestCases.APIViewTestCase):
|
|
|
124
131
|
|
|
125
132
|
@classmethod
|
|
126
133
|
def setUpTestData(cls):
|
|
127
|
-
cls.vrfs =
|
|
134
|
+
cls.vrfs = (
|
|
135
|
+
VRF.objects.create(name="Test VRF 1", rd="65000:1", namespace=Namespace.objects.first()),
|
|
136
|
+
VRF.objects.create(name="Test VRF 2", rd="65000:2", namespace=Namespace.objects.first()),
|
|
137
|
+
VRF.objects.create(name="Test VRF 3", rd="65000:3", namespace=Namespace.objects.first()),
|
|
138
|
+
VRF.objects.create(name="Test VRF 4", rd="65000:4", namespace=Namespace.objects.first()),
|
|
139
|
+
VRF.objects.create(name="Test VRF 5", rd="65000:5", namespace=Namespace.objects.first()),
|
|
140
|
+
)
|
|
128
141
|
cls.devices = Device.objects.all()
|
|
129
142
|
cls.vdcs = VirtualDeviceContext.objects.all()
|
|
130
143
|
locations = Location.objects.filter(location_type__name="Campus")
|
|
@@ -188,7 +201,7 @@ class VRFDeviceAssignmentTest(APIViewTestCases.APIViewTestCase):
|
|
|
188
201
|
},
|
|
189
202
|
{
|
|
190
203
|
"vrf": cls.vrfs[4].pk,
|
|
191
|
-
"virtual_device_context": cls.vdcs[
|
|
204
|
+
"virtual_device_context": cls.vdcs[1].pk,
|
|
192
205
|
},
|
|
193
206
|
]
|
|
194
207
|
cls.bulk_update_data = {
|
|
@@ -1613,7 +1626,7 @@ class IPAddressTest(APIViewTestCases.APIViewTestCase):
|
|
|
1613
1626
|
self.assertHttpStatus(ip2, status.HTTP_201_CREATED)
|
|
1614
1627
|
|
|
1615
1628
|
response = self.client.get(
|
|
1616
|
-
self._get_detail_url(nat_inside) + "?depth=1",
|
|
1629
|
+
self._get_detail_url(nat_inside) + "?depth=1&exclude_m2m=false",
|
|
1617
1630
|
**self.header,
|
|
1618
1631
|
)
|
|
1619
1632
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
@@ -17,6 +17,7 @@ from nautobot.ipam.choices import PrefixTypeChoices, ServiceProtocolChoices
|
|
|
17
17
|
from nautobot.ipam.filters import (
|
|
18
18
|
IPAddressFilterSet,
|
|
19
19
|
IPAddressToInterfaceFilterSet,
|
|
20
|
+
NamespaceFilterSet,
|
|
20
21
|
PrefixFilterSet,
|
|
21
22
|
PrefixLocationAssignmentFilterSet,
|
|
22
23
|
RIRFilterSet,
|
|
@@ -54,6 +55,15 @@ from nautobot.virtualization.models import (
|
|
|
54
55
|
)
|
|
55
56
|
|
|
56
57
|
|
|
58
|
+
class NamespaceTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
|
|
59
|
+
"""Namespace FilterSet tests"""
|
|
60
|
+
|
|
61
|
+
queryset = Namespace.objects.all()
|
|
62
|
+
filterset = NamespaceFilterSet
|
|
63
|
+
tenancy_related_name = "namespaces"
|
|
64
|
+
generic_filter_tests = (("name",),)
|
|
65
|
+
|
|
66
|
+
|
|
57
67
|
class VRFTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilterTestCaseMixin):
|
|
58
68
|
"""VRF Filterset tests
|
|
59
69
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Test IPAM forms."""
|
|
2
2
|
|
|
3
3
|
from django.forms import Form
|
|
4
|
-
from django.test import TestCase
|
|
5
4
|
|
|
5
|
+
from nautobot.core.testing import TestCase
|
|
6
6
|
from nautobot.core.testing.forms import FormTestCases
|
|
7
7
|
from nautobot.extras.models import Status
|
|
8
8
|
from nautobot.ipam import forms, models
|
|
@@ -5,9 +5,9 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
5
5
|
from django.core.exceptions import ValidationError
|
|
6
6
|
from django.db import connection, IntegrityError
|
|
7
7
|
from django.db.models import ProtectedError
|
|
8
|
-
from django.test import TestCase
|
|
9
8
|
import netaddr
|
|
10
9
|
|
|
10
|
+
from nautobot.core.testing import TestCase
|
|
11
11
|
from nautobot.core.testing.models import ModelTestCases
|
|
12
12
|
from nautobot.dcim import choices as dcim_choices
|
|
13
13
|
from nautobot.dcim.models import Device, DeviceType, Interface, Location, LocationType, Module, ModuleBay, ModuleType
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
from django.test import TestCase
|
|
2
|
-
|
|
3
1
|
from nautobot.core.models.querysets import count_related
|
|
2
|
+
from nautobot.core.testing import TestCase
|
|
4
3
|
from nautobot.dcim.models.locations import Location
|
|
5
4
|
from nautobot.ipam.models import Prefix
|
|
6
5
|
from nautobot.ipam.tables import PrefixTable
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from django.test import TestCase
|
|
2
1
|
import netaddr
|
|
3
2
|
|
|
4
3
|
from nautobot.core.forms.utils import parse_numeric_range
|
|
4
|
+
from nautobot.core.testing import TestCase
|
|
5
5
|
from nautobot.extras.models import Status
|
|
6
6
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup
|
|
7
7
|
from nautobot.ipam.utils import add_available_ipaddresses, add_available_vlans
|
|
@@ -51,17 +51,7 @@ from nautobot.users.models import ObjectPermission
|
|
|
51
51
|
from nautobot.virtualization.models import Cluster, ClusterType, VirtualMachine
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
class NamespaceTestCase(
|
|
55
|
-
ViewTestCases.GetObjectViewTestCase,
|
|
56
|
-
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
57
|
-
ViewTestCases.GetObjectNotesViewTestCase,
|
|
58
|
-
ViewTestCases.CreateObjectViewTestCase,
|
|
59
|
-
ViewTestCases.EditObjectViewTestCase,
|
|
60
|
-
ViewTestCases.DeleteObjectViewTestCase,
|
|
61
|
-
ViewTestCases.ListObjectsViewTestCase,
|
|
62
|
-
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
63
|
-
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
64
|
-
):
|
|
54
|
+
class NamespaceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
65
55
|
model = Namespace
|
|
66
56
|
custom_action_required_permissions = {
|
|
67
57
|
"ipam:namespace_vrfs": ["ipam.view_namespace", "ipam.view_vrf"],
|
|
@@ -72,11 +62,18 @@ class NamespaceTestCase(
|
|
|
72
62
|
@classmethod
|
|
73
63
|
def setUpTestData(cls):
|
|
74
64
|
locations = Location.objects.get_for_model(Namespace)
|
|
65
|
+
tenants = Tenant.objects.all()[:2]
|
|
75
66
|
|
|
76
|
-
cls.form_data = {
|
|
67
|
+
cls.form_data = {
|
|
68
|
+
"name": "Namespace X",
|
|
69
|
+
"location": locations[0].pk,
|
|
70
|
+
"tenant": tenants[0].pk,
|
|
71
|
+
"description": "A new Namespace",
|
|
72
|
+
}
|
|
77
73
|
|
|
78
74
|
cls.bulk_edit_data = {
|
|
79
75
|
"description": "New description",
|
|
76
|
+
"tenant": tenants[1].pk,
|
|
80
77
|
"location": locations[1].pk,
|
|
81
78
|
}
|
|
82
79
|
|
|
@@ -165,7 +162,7 @@ class RIRTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
165
162
|
if count > 1:
|
|
166
163
|
self.assertBodyContains(
|
|
167
164
|
response,
|
|
168
|
-
f'<a href="{prefix_list_url}?{urlencode({"rir": rir.name})}" class="badge">{count}</a>',
|
|
165
|
+
f'<a href="{prefix_list_url}?{urlencode({"rir": rir.name})}" class="badge bg-primary">{count}</a>',
|
|
169
166
|
)
|
|
170
167
|
elif count == 1:
|
|
171
168
|
self.assertBodyContains(response, hyperlinked_object(rir.prefixes.first()))
|
|
@@ -250,7 +247,8 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
|
|
|
250
247
|
count = prefix.locations.count()
|
|
251
248
|
if count > 1:
|
|
252
249
|
self.assertBodyContains(
|
|
253
|
-
response,
|
|
250
|
+
response,
|
|
251
|
+
f'<a href="{locations_list_url}?prefixes={prefix.pk}" class="badge bg-primary">{count}</a>',
|
|
254
252
|
)
|
|
255
253
|
elif count == 1:
|
|
256
254
|
self.assertBodyContains(response, hyperlinked_object(prefix.locations.first(), "name"))
|
|
@@ -312,12 +310,16 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
|
|
|
312
310
|
# This validates that both parent prefix and child prefix IPAddresses are present in parent prefix IPAddresses list
|
|
313
311
|
self.assertIn("5.5.10.1/23", strip_tags(content))
|
|
314
312
|
self.assertIn("5.5.10.4/23", strip_tags(content))
|
|
315
|
-
ip_address_tab =
|
|
316
|
-
f'<li role="presentation" class="active"><a href="{url}">IP Addresses <span class="badge">2</span></a></li>'
|
|
317
|
-
)
|
|
313
|
+
ip_address_tab = f'<li class="nav-item" role="presentation"><a class="nav-link active" aria-current="page" href="{url}" aria-controls="ip-addresses" role="tab">IP Addresses <span class="badge bg-primary">2</span></a></li>'
|
|
318
314
|
self.assertInHTML(ip_address_tab, content)
|
|
319
315
|
# Checks if the button is in the content.
|
|
320
|
-
|
|
316
|
+
add_ip_link = (
|
|
317
|
+
reverse("ipam:ipaddress_add")
|
|
318
|
+
+ "?"
|
|
319
|
+
+ urlencode({"address": "5.5.10.2/23", "namespace": str(self.namespace.pk)})
|
|
320
|
+
)
|
|
321
|
+
add_ip_button = f'<a href="{add_ip_link}" class="btn btn-primary"><span class="mdi mdi-plus-thick" aria-hidden="true"></span>Add an IP Address</a>'
|
|
322
|
+
self.assertInHTML(add_ip_button, content)
|
|
321
323
|
|
|
322
324
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
323
325
|
def test_prefix_child_prefixes_table_list(self):
|
|
@@ -341,8 +343,8 @@ class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.List
|
|
|
341
343
|
content = extract_page_body(response.content.decode(response.charset))
|
|
342
344
|
# This validates that both parent prefix and child prefix IPAddresses are present in parent prefix IPAddresses list
|
|
343
345
|
self.assertIn("5.5.10.0/30", strip_tags(content))
|
|
344
|
-
|
|
345
|
-
self.assertInHTML(
|
|
346
|
+
prefixes_tab = f'<li role="presentation" class="nav-item"><a class="nav-link active" href="{url}" aria-controls="prefixes" role="tab" aria-current="page">Child Prefixes <span class="badge bg-primary">1</span></a></li>'
|
|
347
|
+
self.assertInHTML(prefixes_tab, content)
|
|
346
348
|
# Checks if the button is in the content.
|
|
347
349
|
self.assertInHTML("""<span class="mdi mdi-plus-thick" aria-hidden="true"></span>Add Child Prefix""", content)
|
|
348
350
|
|
|
@@ -752,6 +754,7 @@ class IPAddressMergeTestCase(ModelViewTestCase):
|
|
|
752
754
|
self.add_permissions("ipam.change_ipaddress")
|
|
753
755
|
num_ips_before = IPAddress.objects.all().count()
|
|
754
756
|
ips = IPAddress.objects.all().exclude(pk__in=[self.dup_ip_1.pk, self.dup_ip_2.pk, self.dup_ip_3.pk])
|
|
757
|
+
self.assertGreaterEqual(len(ips), 6)
|
|
755
758
|
ip_ct = ContentType.objects.get_for_model(IPAddress)
|
|
756
759
|
locations = Location.objects.all()
|
|
757
760
|
location_ct = ContentType.objects.get_for_model(Location)
|
|
@@ -1203,6 +1206,6 @@ class ServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1203
1206
|
response_content = response.content.decode(response.charset)
|
|
1204
1207
|
self.assertHttpStatus(response, 200)
|
|
1205
1208
|
self.assertInHTML(
|
|
1206
|
-
' <strong
|
|
1209
|
+
' <strong>Ports</strong>: <ul class="errorlist"><li>invalid literal for int() with base 10: '[106'</li></ul>',
|
|
1207
1210
|
response_content,
|
|
1208
1211
|
)
|
nautobot/ipam/ui.py
CHANGED
|
@@ -10,29 +10,12 @@ from nautobot.core.ui.object_detail import (
|
|
|
10
10
|
Button,
|
|
11
11
|
KeyValueTablePanel,
|
|
12
12
|
ObjectFieldsPanel,
|
|
13
|
-
ObjectsTablePanel,
|
|
14
13
|
)
|
|
15
14
|
from nautobot.core.views.utils import get_obj_from_context
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
19
18
|
|
|
20
|
-
# TODO: can be removed as a part of NAUTOBOT-1051
|
|
21
|
-
class PrefixChildTablePanel(ObjectsTablePanel):
|
|
22
|
-
def should_render(self, context: Context):
|
|
23
|
-
if not super().should_render(context):
|
|
24
|
-
return False
|
|
25
|
-
return context.get("active_tab") == "prefixes"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# TODO: can be removed as a part of NAUTOBOT-1051
|
|
29
|
-
class IPAddressTablePanel(ObjectsTablePanel):
|
|
30
|
-
def should_render(self, context: Context):
|
|
31
|
-
if not super().should_render(context):
|
|
32
|
-
return False
|
|
33
|
-
return context.get("active_tab") == "ip-addresses"
|
|
34
|
-
|
|
35
|
-
|
|
36
19
|
class AddChildPrefixButton(Button):
|
|
37
20
|
"""Custom button to add a child prefix inside a Prefix detail view."""
|
|
38
21
|
|
|
@@ -146,7 +146,9 @@ def process_vrfs(apps):
|
|
|
146
146
|
Namespace = apps.get_model("ipam", "Namespace")
|
|
147
147
|
VRF = apps.get_model("ipam", "VRF")
|
|
148
148
|
|
|
149
|
-
global_ns = Namespace.objects.
|
|
149
|
+
global_ns, _ = Namespace.objects.get_or_create(
|
|
150
|
+
name=GLOBAL_NS, defaults={"description": "Default Global namespace. Created by Nautobot."}
|
|
151
|
+
)
|
|
150
152
|
vrfs = VRF.objects.all().order_by("name", "rd")
|
|
151
153
|
unique_non_empty_vrfs = vrfs.filter(enforce_unique=True).exclude(ip_addresses__isnull=True, prefixes__isnull=True)
|
|
152
154
|
# At the point in the migration where we iterate through vrfs in global_ns_vrfs, every vrf that
|
|
@@ -257,6 +259,11 @@ def process_ip_addresses(apps):
|
|
|
257
259
|
broadcast = new_parent_cidr[-1]
|
|
258
260
|
# This can result in duplicate Prefixes being created in the global_ns but that will be
|
|
259
261
|
# cleaned up subsequently in `process_prefix_duplicates`.
|
|
262
|
+
if orphaned_ip.vrf and orphaned_ip.vrf.namespace:
|
|
263
|
+
prefix_namespace = orphaned_ip.vrf.namespace
|
|
264
|
+
else:
|
|
265
|
+
prefix_namespace = global_ns
|
|
266
|
+
|
|
260
267
|
new_parent = Prefix.objects.create(
|
|
261
268
|
ip_version=orphaned_ip.ip_version,
|
|
262
269
|
network=network,
|
|
@@ -264,7 +271,7 @@ def process_ip_addresses(apps):
|
|
|
264
271
|
tenant=orphaned_ip.tenant,
|
|
265
272
|
vrf=orphaned_ip.vrf,
|
|
266
273
|
prefix_length=prefix_length,
|
|
267
|
-
namespace=
|
|
274
|
+
namespace=prefix_namespace,
|
|
268
275
|
description=DESCRIPTION,
|
|
269
276
|
)
|
|
270
277
|
orphaned_ip.parent = new_parent
|
|
@@ -296,6 +303,13 @@ def process_prefix_duplicates(apps):
|
|
|
296
303
|
Prefix = apps.get_model("ipam", "Prefix")
|
|
297
304
|
global_namespace = Namespace.objects.get(name=GLOBAL_NS)
|
|
298
305
|
|
|
306
|
+
# First, assign any remaining prefixes with null namespace to Global namespace
|
|
307
|
+
null_namespace_prefixes = Prefix.objects.filter(namespace__isnull=True)
|
|
308
|
+
if null_namespace_prefixes.exists():
|
|
309
|
+
if "test" not in sys.argv:
|
|
310
|
+
print(f">>> Assigning {null_namespace_prefixes.count()} prefixes with null namespace to Global namespace")
|
|
311
|
+
null_namespace_prefixes.update(namespace=global_namespace)
|
|
312
|
+
|
|
299
313
|
namespaces = list(Namespace.objects.all())
|
|
300
314
|
# Always start with the Global Namespace.
|
|
301
315
|
namespaces.remove(global_namespace)
|
nautobot/ipam/utils/testing.py
CHANGED
|
@@ -5,8 +5,6 @@ import random
|
|
|
5
5
|
from django.apps import apps
|
|
6
6
|
from netaddr import IPNetwork
|
|
7
7
|
|
|
8
|
-
from nautobot.ipam.models import get_default_namespace_pk
|
|
9
|
-
|
|
10
8
|
# Calculate the probabilities to use for the maybe_subdivide() function defined below.
|
|
11
9
|
|
|
12
10
|
# Frequency of IPv4 (leaf, network) Prefixes by each given mask length in a "realistic" data set.
|
|
@@ -129,7 +127,15 @@ def create_prefixes_and_ips(initial_subnet: str, apps=apps, seed="Nautobot"): #
|
|
|
129
127
|
|
|
130
128
|
all_tenants = list(Tenant.objects.all())
|
|
131
129
|
if hasattr(VRF, "namespace"):
|
|
132
|
-
|
|
130
|
+
# Simulate mid-migration state: create Global namespace and assign all VRFs to it
|
|
131
|
+
# This creates more realistic test conditions for namespace-aware migration logic
|
|
132
|
+
Namespace = apps.get_model("ipam", "Namespace")
|
|
133
|
+
global_ns, _ = Namespace.objects.get_or_create(
|
|
134
|
+
name="Global", defaults={"description": "Default Global namespace. Created by Nautobot."}
|
|
135
|
+
)
|
|
136
|
+
# Pre-assign all VRFs to Global namespace to simulate early migration state
|
|
137
|
+
VRF.objects.all().update(namespace=global_ns)
|
|
138
|
+
all_vrfs = list(VRF.objects.filter(namespace_id=global_ns.pk))
|
|
133
139
|
else:
|
|
134
140
|
all_vrfs = list(VRF.objects.all())
|
|
135
141
|
|