nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__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/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +111 -0
- nautobot/cloud/filters.py +184 -0
- nautobot/cloud/forms.py +333 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +247 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +173 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +113 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +96 -28
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +34 -4
- nautobot/core/jobs/__init__.py +17 -6
- nautobot/core/jobs/cleanup.py +100 -0
- nautobot/core/jobs/groups.py +38 -0
- nautobot/core/management/commands/generate_test_data.py +116 -3
- nautobot/core/models/__init__.py +34 -9
- nautobot/core/models/generics.py +19 -3
- nautobot/core/models/name_color_content_types.py +7 -28
- nautobot/core/models/querysets.py +4 -3
- nautobot/core/models/tree_queries.py +1 -1
- nautobot/core/models/utils.py +21 -5
- nautobot/core/settings.py +4 -30
- nautobot/core/settings.yaml +34 -27
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +127 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +13 -23
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +51 -13
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
- nautobot/core/tests/integration/test_swagger.py +1 -1
- nautobot/core/tests/nautobot_config.py +0 -1
- nautobot/core/tests/runner.py +2 -2
- nautobot/core/tests/test_api.py +1 -0
- nautobot/core/tests/test_authentication.py +7 -2
- nautobot/core/tests/test_filters.py +11 -9
- nautobot/core/tests/test_forms.py +9 -0
- nautobot/core/tests/test_graphql.py +27 -16
- nautobot/core/tests/test_jobs.py +123 -74
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +118 -66
- nautobot/core/views/mixins.py +104 -35
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +79 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +249 -18
- nautobot/dcim/filters/__init__.py +369 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +817 -109
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +861 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +275 -106
- nautobot/dcim/models/devices.py +466 -13
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +231 -59
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +15 -7
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +691 -208
- nautobot/dcim/tests/test_filters.py +836 -217
- nautobot/dcim/tests/test_models.py +1072 -39
- nautobot/dcim/tests/test_views.py +1488 -358
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1107 -120
- nautobot/extras/__init__.py +0 -1
- nautobot/extras/api/serializers.py +115 -3
- nautobot/extras/api/urls.py +12 -0
- nautobot/extras/api/views.py +125 -7
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +422 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +17 -4
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +16 -13
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
- nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
- nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
- nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
- nautobot/extras/migrations/0111_metadata.py +162 -0
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
- nautobot/extras/migrations/0113_saved_views.py +77 -0
- nautobot/extras/models/__init__.py +15 -1
- nautobot/extras/models/change_logging.py +3 -3
- nautobot/extras/models/contacts.py +4 -0
- nautobot/extras/models/customfields.py +18 -3
- nautobot/extras/models/groups.py +389 -225
- nautobot/extras/models/jobs.py +4 -84
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +116 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +96 -114
- nautobot/extras/tables.py +171 -47
- nautobot/extras/templates/extras/dynamicgroup.html +44 -15
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
- nautobot/extras/templates/extras/job.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +0 -11
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +501 -22
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +4 -4
- nautobot/extras/tests/test_models.py +499 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +565 -28
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +56 -45
- nautobot/extras/views.py +585 -96
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +14 -1
- nautobot/ipam/forms.py +9 -5
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +23 -9
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +1 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +8 -3
- nautobot/ipam/tests/test_querysets.py +1 -1
- nautobot/ipam/tests/test_views.py +3 -2
- nautobot/ipam/urls.py +2 -2
- nautobot/ipam/views.py +25 -28
- nautobot/project-static/css/base.css +20 -1
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +884 -80
- nautobot/project-static/docs/apps/index.html +884 -80
- nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
- nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
- nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
- nautobot/project-static/docs/development/apps/api/setup.html +884 -80
- nautobot/project-static/docs/development/apps/api/testing.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
- nautobot/project-static/docs/development/apps/index.html +884 -80
- nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
- nautobot/project-static/docs/development/core/application-registry.html +884 -80
- nautobot/project-static/docs/development/core/best-practices.html +885 -80
- nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
- nautobot/project-static/docs/development/core/caching.html +884 -80
- nautobot/project-static/docs/development/core/controllers.html +884 -80
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
- nautobot/project-static/docs/development/core/generic-views.html +884 -80
- nautobot/project-static/docs/development/core/getting-started.html +884 -80
- nautobot/project-static/docs/development/core/homepage.html +884 -80
- nautobot/project-static/docs/development/core/index.html +884 -91
- nautobot/project-static/docs/development/core/model-checklist.html +887 -81
- nautobot/project-static/docs/development/core/model-features.html +884 -80
- nautobot/project-static/docs/development/core/natural-keys.html +884 -80
- nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
- nautobot/project-static/docs/development/core/release-checklist.html +887 -83
- nautobot/project-static/docs/development/core/role-internals.html +884 -80
- nautobot/project-static/docs/development/core/settings.html +884 -80
- nautobot/project-static/docs/development/core/style-guide.html +885 -81
- nautobot/project-static/docs/development/core/templates.html +896 -81
- nautobot/project-static/docs/development/core/testing.html +884 -80
- nautobot/project-static/docs/development/core/user-preferences.html +884 -80
- nautobot/project-static/docs/development/index.html +884 -80
- nautobot/project-static/docs/development/jobs/index.html +1247 -457
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
- nautobot/project-static/docs/index.html +13 -8228
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
- nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
- nautobot/project-static/docs/models/dcim/module.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
- nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
- nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
- nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
- nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
- nautobot/project-static/docs/models/extras/role.html +15 -0
- nautobot/project-static/docs/models/extras/savedview.html +15 -0
- nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
- nautobot/project-static/docs/models/extras/status.html +15 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +892 -81
- nautobot/project-static/docs/overview/design_philosophy.html +886 -82
- nautobot/project-static/docs/overview/index.html +9032 -13
- nautobot/project-static/docs/release-notes/index.html +887 -83
- nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
- nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
- nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
- nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
- nautobot/project-static/docs/requirements.txt +4 -4
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +335 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
- nautobot/project-static/docs/user-guide/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
- nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +22 -18
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/tables.py +15 -5
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -8,9 +8,13 @@ from django.db.models import Q
|
|
|
8
8
|
from django.test import override_settings
|
|
9
9
|
from django.urls import reverse
|
|
10
10
|
from netaddr import EUI
|
|
11
|
-
import pytz
|
|
12
11
|
import yaml
|
|
13
12
|
|
|
13
|
+
try:
|
|
14
|
+
import zoneinfo
|
|
15
|
+
except ImportError: # python 3.8
|
|
16
|
+
from backports import zoneinfo
|
|
17
|
+
|
|
14
18
|
from nautobot.circuits.choices import CircuitTerminationSideChoices
|
|
15
19
|
from nautobot.circuits.models import Circuit, CircuitTermination, CircuitType, Provider
|
|
16
20
|
from nautobot.core.templatetags.buttons import job_export_url, job_import_url
|
|
@@ -81,6 +85,10 @@ from nautobot.dcim.models import (
|
|
|
81
85
|
Location,
|
|
82
86
|
LocationType,
|
|
83
87
|
Manufacturer,
|
|
88
|
+
Module,
|
|
89
|
+
ModuleBay,
|
|
90
|
+
ModuleBayTemplate,
|
|
91
|
+
ModuleType,
|
|
84
92
|
Platform,
|
|
85
93
|
PowerFeed,
|
|
86
94
|
PowerOutlet,
|
|
@@ -167,7 +175,6 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
167
175
|
lt3 = LocationType.objects.get(name="Building")
|
|
168
176
|
lt4 = LocationType.objects.get(name="Floor")
|
|
169
177
|
for lt in [lt1, lt2, lt3, lt4]:
|
|
170
|
-
lt.validated_save()
|
|
171
178
|
lt.content_types.add(ContentType.objects.get_for_model(RackGroup))
|
|
172
179
|
# Deletable Location Types
|
|
173
180
|
LocationType.objects.create(name="Delete Me 1")
|
|
@@ -178,7 +185,7 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
178
185
|
# so we need to make sure we're not trying to introduce a reference loop to the LocationType tree...
|
|
179
186
|
cls.form_data = {
|
|
180
187
|
"name": "Intermediate 2",
|
|
181
|
-
|
|
188
|
+
"parent": lt1.pk,
|
|
182
189
|
"description": "Another intermediate type",
|
|
183
190
|
"content_types": [
|
|
184
191
|
ContentType.objects.get_for_model(Rack).pk,
|
|
@@ -188,7 +195,7 @@ class LocationTypeTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
def _get_queryset(self):
|
|
191
|
-
return super()._get_queryset().order_by("last_updated")
|
|
198
|
+
return super()._get_queryset().order_by("-last_updated")
|
|
192
199
|
|
|
193
200
|
|
|
194
201
|
class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
@@ -201,8 +208,6 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
201
208
|
lt1 = LocationType.objects.get(name="Campus")
|
|
202
209
|
lt2 = LocationType.objects.get(name="Building")
|
|
203
210
|
lt3 = LocationType.objects.get(name="Floor")
|
|
204
|
-
for lt in [lt1, lt2, lt3]:
|
|
205
|
-
lt.validated_save()
|
|
206
211
|
|
|
207
212
|
status = Status.objects.get_for_model(Location).first()
|
|
208
213
|
tenant = Tenant.objects.first()
|
|
@@ -228,7 +233,7 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
228
233
|
"tenant": tenant.pk,
|
|
229
234
|
"facility": "Facility X",
|
|
230
235
|
"asn": 65001,
|
|
231
|
-
"time_zone":
|
|
236
|
+
"time_zone": zoneinfo.ZoneInfo("UTC"),
|
|
232
237
|
"physical_address": "742 Evergreen Terrace, Springfield, USA",
|
|
233
238
|
"shipping_address": "742 Evergreen Terrace, Springfield, USA",
|
|
234
239
|
"latitude": Decimal("35.780000"),
|
|
@@ -248,9 +253,12 @@ class LocationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
248
253
|
"tenant": tenant.pk,
|
|
249
254
|
"status": Status.objects.get_for_model(Location).last().pk,
|
|
250
255
|
"asn": 65009,
|
|
251
|
-
"time_zone":
|
|
256
|
+
"time_zone": zoneinfo.ZoneInfo("US/Eastern"),
|
|
252
257
|
}
|
|
253
258
|
|
|
259
|
+
def _get_queryset(self):
|
|
260
|
+
return super()._get_queryset().filter(location_type__name="Campus")
|
|
261
|
+
|
|
254
262
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
255
263
|
def test_create_child_location_under_a_non_globally_unique_named_parent_location(
|
|
256
264
|
self,
|
|
@@ -751,19 +759,23 @@ class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
751
759
|
|
|
752
760
|
@classmethod
|
|
753
761
|
def setUpTestData(cls):
|
|
754
|
-
# FIXME(jathan): This has to be replaced with# `get_deletable_object` and
|
|
755
|
-
# `get_deletable_object_pks` but this is a workaround just so all of these objects are
|
|
756
|
-
# deletable for now.
|
|
757
|
-
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
758
|
-
Device.objects.all().delete()
|
|
759
|
-
DeviceType.objects.all().delete()
|
|
760
|
-
Platform.objects.all().delete()
|
|
761
|
-
|
|
762
762
|
cls.form_data = {
|
|
763
763
|
"name": "Manufacturer X",
|
|
764
764
|
"description": "A new manufacturer",
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
+
def get_deletable_object(self):
|
|
768
|
+
mf = Manufacturer.objects.create(name="Deletable Manufacturer")
|
|
769
|
+
return mf
|
|
770
|
+
|
|
771
|
+
def get_deletable_object_pks(self):
|
|
772
|
+
mfs = [
|
|
773
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 1"),
|
|
774
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 2"),
|
|
775
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 3"),
|
|
776
|
+
]
|
|
777
|
+
return [mf.pk for mf in mfs]
|
|
778
|
+
|
|
767
779
|
|
|
768
780
|
# TODO: Change base class to PrimaryObjectViewTestCase
|
|
769
781
|
# Blocked by absence of bulk import view for DeviceTypes
|
|
@@ -803,8 +815,7 @@ class DeviceTypeTestCase(
|
|
|
803
815
|
}
|
|
804
816
|
|
|
805
817
|
cls.bulk_edit_data = {
|
|
806
|
-
"
|
|
807
|
-
"u_height": 3,
|
|
818
|
+
"u_height": 0,
|
|
808
819
|
"is_full_depth": False,
|
|
809
820
|
}
|
|
810
821
|
|
|
@@ -817,29 +828,32 @@ class DeviceTypeTestCase(
|
|
|
817
828
|
|
|
818
829
|
yaml_import_url = reverse("dcim:devicetype_import")
|
|
819
830
|
csv_import_url = job_import_url(ContentType.objects.get_for_model(DeviceType))
|
|
820
|
-
#
|
|
831
|
+
# Dropdown provides both YAML/JSON and CSV import as options
|
|
821
832
|
self.assertInHTML(
|
|
822
|
-
f'<a
|
|
823
|
-
|
|
833
|
+
f'<a href="{yaml_import_url}"><span class="mdi mdi-database-import text-muted" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
|
|
834
|
+
content,
|
|
835
|
+
)
|
|
836
|
+
self.assertInHTML(
|
|
837
|
+
f'<a href="{csv_import_url}"><span class="mdi mdi-database-import text-muted" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
|
|
824
838
|
content,
|
|
825
839
|
)
|
|
826
|
-
# Dropdown provides both YAML/JSON and CSV import as options
|
|
827
|
-
self.assertInHTML(f'<a href="{yaml_import_url}">JSON/YAML format (single record)</a>', content)
|
|
828
|
-
self.assertInHTML(f'<a href="{csv_import_url}">CSV format (multiple records)</a>', content)
|
|
829
840
|
|
|
830
841
|
export_url = job_export_url()
|
|
831
842
|
# Export is a little trickier to check since it's done as a form submission rather than an <a> element.
|
|
832
843
|
self.assertIn(f'<form action="{export_url}" method="post">', content)
|
|
833
844
|
self.assertInHTML(
|
|
834
|
-
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(
|
|
845
|
+
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(self.model).pk}">',
|
|
835
846
|
content,
|
|
836
847
|
)
|
|
837
848
|
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
838
849
|
self.assertInHTML(
|
|
839
|
-
'<button type="submit" class="
|
|
850
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as YAML</button>',
|
|
851
|
+
content,
|
|
852
|
+
)
|
|
853
|
+
self.assertInHTML(
|
|
854
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as CSV</button>',
|
|
840
855
|
content,
|
|
841
856
|
)
|
|
842
|
-
self.assertInHTML('<button type="submit" class="btn btn-link">CSV format</button>', content)
|
|
843
857
|
|
|
844
858
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
845
859
|
def test_import_objects(self):
|
|
@@ -920,6 +934,13 @@ device-bays:
|
|
|
920
934
|
- name: Device Bay 1
|
|
921
935
|
- name: Device Bay 2
|
|
922
936
|
- name: Device Bay 3
|
|
937
|
+
module-bays:
|
|
938
|
+
- name: Module Bay 1
|
|
939
|
+
position: 1
|
|
940
|
+
- name: Module Bay 2
|
|
941
|
+
position: 2
|
|
942
|
+
- name: Module Bay 3
|
|
943
|
+
position: 3
|
|
923
944
|
"""
|
|
924
945
|
|
|
925
946
|
# Add all required permissions to the test user
|
|
@@ -934,6 +955,7 @@ device-bays:
|
|
|
934
955
|
"dcim.add_frontporttemplate",
|
|
935
956
|
"dcim.add_rearporttemplate",
|
|
936
957
|
"dcim.add_devicebaytemplate",
|
|
958
|
+
"dcim.add_modulebaytemplate",
|
|
937
959
|
)
|
|
938
960
|
|
|
939
961
|
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
@@ -944,47 +966,51 @@ device-bays:
|
|
|
944
966
|
|
|
945
967
|
# Verify all of the components were created
|
|
946
968
|
self.assertEqual(dt.console_port_templates.count(), 3)
|
|
947
|
-
cp1 =
|
|
969
|
+
cp1 = dt.console_port_templates.first()
|
|
948
970
|
self.assertEqual(cp1.name, "Console Port 1")
|
|
949
971
|
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
|
950
972
|
|
|
951
973
|
self.assertEqual(dt.console_server_port_templates.count(), 3)
|
|
952
|
-
csp1 =
|
|
974
|
+
csp1 = dt.console_server_port_templates.first()
|
|
953
975
|
self.assertEqual(csp1.name, "Console Server Port 1")
|
|
954
976
|
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
|
955
977
|
|
|
956
978
|
self.assertEqual(dt.power_port_templates.count(), 3)
|
|
957
|
-
pp1 =
|
|
979
|
+
pp1 = dt.power_port_templates.first()
|
|
958
980
|
self.assertEqual(pp1.name, "Power Port 1")
|
|
959
981
|
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
|
960
982
|
|
|
961
983
|
self.assertEqual(dt.power_outlet_templates.count(), 3)
|
|
962
|
-
po1 =
|
|
984
|
+
po1 = dt.power_outlet_templates.first()
|
|
963
985
|
self.assertEqual(po1.name, "Power Outlet 1")
|
|
964
986
|
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
|
965
987
|
self.assertEqual(po1.power_port_template, pp1)
|
|
966
988
|
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
|
967
989
|
|
|
968
990
|
self.assertEqual(dt.interface_templates.count(), 3)
|
|
969
|
-
iface1 =
|
|
991
|
+
iface1 = dt.interface_templates.first()
|
|
970
992
|
self.assertEqual(iface1.name, "Interface 1")
|
|
971
993
|
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
|
972
994
|
self.assertTrue(iface1.mgmt_only)
|
|
973
995
|
|
|
974
996
|
self.assertEqual(dt.rear_port_templates.count(), 3)
|
|
975
|
-
rp1 =
|
|
997
|
+
rp1 = dt.rear_port_templates.first()
|
|
976
998
|
self.assertEqual(rp1.name, "Rear Port 1")
|
|
977
999
|
|
|
978
1000
|
self.assertEqual(dt.front_port_templates.count(), 3)
|
|
979
|
-
fp1 =
|
|
1001
|
+
fp1 = dt.front_port_templates.first()
|
|
980
1002
|
self.assertEqual(fp1.name, "Front Port 1")
|
|
981
1003
|
self.assertEqual(fp1.rear_port_template, rp1)
|
|
982
1004
|
self.assertEqual(fp1.rear_port_position, 1)
|
|
983
1005
|
|
|
984
1006
|
self.assertEqual(dt.device_bay_templates.count(), 3)
|
|
985
|
-
db1 =
|
|
1007
|
+
db1 = dt.device_bay_templates.first()
|
|
986
1008
|
self.assertEqual(db1.name, "Device Bay 1")
|
|
987
1009
|
|
|
1010
|
+
self.assertEqual(dt.module_bay_templates.count(), 3)
|
|
1011
|
+
mb1 = dt.module_bay_templates.first()
|
|
1012
|
+
self.assertEqual(mb1.name, "Module Bay 1")
|
|
1013
|
+
|
|
988
1014
|
def test_import_objects_unknown_type_enums(self):
|
|
989
1015
|
"""
|
|
990
1016
|
YAML import of data with `type` values that we don't recognize should remap those to "other" rather than fail.
|
|
@@ -1023,6 +1049,13 @@ device-bays:
|
|
|
1023
1049
|
- name: Device Bay of Uncertain Type
|
|
1024
1050
|
type: unknown # should be ignored
|
|
1025
1051
|
- name: Device Bay of Unspecified Type
|
|
1052
|
+
module-bays:
|
|
1053
|
+
- name: Module Bay 1
|
|
1054
|
+
position: 1
|
|
1055
|
+
- name: Module Bay 2
|
|
1056
|
+
position: 2
|
|
1057
|
+
- name: Module Bay 3
|
|
1058
|
+
position: 3
|
|
1026
1059
|
"""
|
|
1027
1060
|
# Add all required permissions to the test user
|
|
1028
1061
|
self.add_permissions(
|
|
@@ -1037,6 +1070,7 @@ device-bays:
|
|
|
1037
1070
|
"dcim.add_frontporttemplate",
|
|
1038
1071
|
"dcim.add_rearporttemplate",
|
|
1039
1072
|
"dcim.add_devicebaytemplate",
|
|
1073
|
+
"dcim.add_modulebaytemplate",
|
|
1040
1074
|
)
|
|
1041
1075
|
|
|
1042
1076
|
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
@@ -1085,6 +1119,12 @@ device-bays:
|
|
|
1085
1119
|
self.assertEqual(dt.device_bay_templates.count(), 2)
|
|
1086
1120
|
# DeviceBayTemplate doesn't have a type field.
|
|
1087
1121
|
|
|
1122
|
+
self.assertEqual(dt.module_bay_templates.count(), 3)
|
|
1123
|
+
# ModuleBayTemplate doesn't have a type field.
|
|
1124
|
+
mbt = ModuleBayTemplate.objects.filter(device_type=dt).first()
|
|
1125
|
+
self.assertEqual(mbt.position, "1")
|
|
1126
|
+
self.assertEqual(mbt.name, "Module Bay 1")
|
|
1127
|
+
|
|
1088
1128
|
def test_devicetype_export(self):
|
|
1089
1129
|
url = reverse("dcim:devicetype_list")
|
|
1090
1130
|
self.add_permissions("dcim.view_devicetype")
|
|
@@ -1135,128 +1175,501 @@ device-bays:
|
|
|
1135
1175
|
self.assertIn("failed validation", response.content.decode(response.charset))
|
|
1136
1176
|
|
|
1137
1177
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1178
|
+
class ModuleTypeTestCase(
|
|
1179
|
+
ViewTestCases.GetObjectViewTestCase,
|
|
1180
|
+
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
1181
|
+
ViewTestCases.CreateObjectViewTestCase,
|
|
1182
|
+
ViewTestCases.EditObjectViewTestCase,
|
|
1183
|
+
ViewTestCases.DeleteObjectViewTestCase,
|
|
1184
|
+
ViewTestCases.ListObjectsViewTestCase,
|
|
1185
|
+
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
1186
|
+
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
1187
|
+
):
|
|
1188
|
+
model = ModuleType
|
|
1145
1189
|
|
|
1146
1190
|
@classmethod
|
|
1147
1191
|
def setUpTestData(cls):
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1152
|
-
)
|
|
1192
|
+
manufacturers = Manufacturer.objects.all()[:2]
|
|
1193
|
+
Module.objects.all().delete()
|
|
1194
|
+
ModuleType.objects.all().delete()
|
|
1153
1195
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1196
|
+
ModuleType.objects.create(
|
|
1197
|
+
model="Test Module Type 1",
|
|
1198
|
+
manufacturer=manufacturers[0],
|
|
1199
|
+
)
|
|
1200
|
+
ModuleType.objects.create(
|
|
1201
|
+
model="Test Module Type 2",
|
|
1202
|
+
manufacturer=manufacturers[0],
|
|
1203
|
+
)
|
|
1204
|
+
ModuleType.objects.create(
|
|
1205
|
+
model="Test Module Type 3",
|
|
1206
|
+
manufacturer=manufacturers[0],
|
|
1207
|
+
)
|
|
1208
|
+
ModuleType.objects.create(
|
|
1209
|
+
model="Test Module Type 4",
|
|
1210
|
+
manufacturer=manufacturers[1],
|
|
1211
|
+
)
|
|
1157
1212
|
|
|
1158
1213
|
cls.form_data = {
|
|
1159
|
-
"
|
|
1160
|
-
"
|
|
1161
|
-
"
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
cls.bulk_create_data = {
|
|
1165
|
-
"device_type": devicetypes[1].pk,
|
|
1166
|
-
"name_pattern": "Console Port Template [4-6]",
|
|
1167
|
-
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1214
|
+
"manufacturer": manufacturers[0].pk,
|
|
1215
|
+
"model": "Test Module Type X",
|
|
1216
|
+
"part_number": "123ABC",
|
|
1217
|
+
"tags": [t.pk for t in Tag.objects.get_for_model(ModuleType)],
|
|
1168
1218
|
}
|
|
1169
1219
|
|
|
1170
1220
|
cls.bulk_edit_data = {
|
|
1171
|
-
"
|
|
1221
|
+
"manufacturer": manufacturers[1].pk,
|
|
1172
1222
|
}
|
|
1173
1223
|
|
|
1224
|
+
def test_list_has_correct_links(self):
|
|
1225
|
+
"""Assert that the ModuleType list view has import/export buttons for both CSV and YAML/JSON formats."""
|
|
1226
|
+
self.add_permissions("dcim.add_moduletype", "dcim.view_moduletype")
|
|
1227
|
+
response = self.client.get(reverse("dcim:moduletype_list"))
|
|
1228
|
+
self.assertHttpStatus(response, 200)
|
|
1229
|
+
content = extract_page_body(response.content.decode(response.charset))
|
|
1174
1230
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1231
|
+
yaml_import_url = reverse("dcim:moduletype_import")
|
|
1232
|
+
csv_import_url = job_import_url(ContentType.objects.get_for_model(ModuleType))
|
|
1233
|
+
# Dropdown provides both YAML/JSON and CSV import as options
|
|
1234
|
+
self.assertInHTML(
|
|
1235
|
+
f'<a href="{yaml_import_url}"><span class="mdi mdi-database-import text-muted" aria-hidden="true"></span> Import from JSON/YAML (single record)</a>',
|
|
1236
|
+
content,
|
|
1237
|
+
)
|
|
1238
|
+
self.assertInHTML(
|
|
1239
|
+
f'<a href="{csv_import_url}"><span class="mdi mdi-database-import text-muted" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
|
|
1240
|
+
content,
|
|
1184
1241
|
)
|
|
1185
1242
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
cls.bulk_edit_data = {
|
|
1203
|
-
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1208
|
-
model = PowerPortTemplate
|
|
1243
|
+
export_url = job_export_url()
|
|
1244
|
+
# Export is a little trickier to check since it's done as a form submission rather than an <a> element.
|
|
1245
|
+
self.assertIn(f'<form action="{export_url}" method="post">', content)
|
|
1246
|
+
self.assertInHTML(
|
|
1247
|
+
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(self.model).pk}">',
|
|
1248
|
+
content,
|
|
1249
|
+
)
|
|
1250
|
+
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
1251
|
+
self.assertInHTML(
|
|
1252
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as YAML</button>',
|
|
1253
|
+
content,
|
|
1254
|
+
)
|
|
1255
|
+
self.assertInHTML(
|
|
1256
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as CSV</button>',
|
|
1257
|
+
content,
|
|
1258
|
+
)
|
|
1209
1259
|
|
|
1210
|
-
@
|
|
1211
|
-
def
|
|
1260
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1261
|
+
def test_import_objects(self):
|
|
1262
|
+
"""
|
|
1263
|
+
Custom import test for YAML-based imports (versus CSV)
|
|
1264
|
+
"""
|
|
1265
|
+
# Note use of "power-outlets.power_port" (not "power_port_template") and "front-ports.rear_port"
|
|
1266
|
+
# (not "rear_port_template"). Note also inclusion of "slug" even though we removed DeviceType.slug in 2.0.
|
|
1267
|
+
# This is intentional as we are testing backwards compatibility with the netbox/devicetype-library repository.
|
|
1212
1268
|
manufacturer = Manufacturer.objects.first()
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1269
|
+
IMPORT_DATA = f"""
|
|
1270
|
+
manufacturer: {manufacturer.name}
|
|
1271
|
+
model: TEST-1000
|
|
1272
|
+
slug: test-1000
|
|
1273
|
+
console-ports:
|
|
1274
|
+
- name: Console Port 1
|
|
1275
|
+
type: de-9
|
|
1276
|
+
- name: Console Port 2
|
|
1277
|
+
type: de-9
|
|
1278
|
+
- name: Console Port 3
|
|
1279
|
+
type: de-9
|
|
1280
|
+
console-server-ports:
|
|
1281
|
+
- name: Console Server Port 1
|
|
1282
|
+
type: rj-45
|
|
1283
|
+
- name: Console Server Port 2
|
|
1284
|
+
type: rj-45
|
|
1285
|
+
- name: Console Server Port 3
|
|
1286
|
+
type: rj-45
|
|
1287
|
+
power-ports:
|
|
1288
|
+
- name: Power Port 1
|
|
1289
|
+
type: iec-60320-c14
|
|
1290
|
+
- name: Power Port 2
|
|
1291
|
+
type: iec-60320-c14
|
|
1292
|
+
- name: Power Port 3
|
|
1293
|
+
type: iec-60320-c14
|
|
1294
|
+
power-outlets:
|
|
1295
|
+
- name: Power Outlet 1
|
|
1296
|
+
type: iec-60320-c13
|
|
1297
|
+
power_port: Power Port 1
|
|
1298
|
+
feed_leg: A
|
|
1299
|
+
- name: Power Outlet 2
|
|
1300
|
+
type: iec-60320-c13
|
|
1301
|
+
power_port: Power Port 1
|
|
1302
|
+
feed_leg: A
|
|
1303
|
+
- name: Power Outlet 3
|
|
1304
|
+
type: iec-60320-c13
|
|
1305
|
+
power_port: Power Port 1
|
|
1306
|
+
feed_leg: A
|
|
1307
|
+
interfaces:
|
|
1308
|
+
- name: Interface 1
|
|
1309
|
+
type: 1000base-t
|
|
1310
|
+
mgmt_only: true
|
|
1311
|
+
- name: Interface 2
|
|
1312
|
+
type: 1000base-t
|
|
1313
|
+
- name: Interface 3
|
|
1314
|
+
type: 1000base-t
|
|
1315
|
+
rear-ports:
|
|
1316
|
+
- name: Rear Port 1
|
|
1317
|
+
type: 8p8c
|
|
1318
|
+
- name: Rear Port 2
|
|
1319
|
+
type: 8p8c
|
|
1320
|
+
- name: Rear Port 3
|
|
1321
|
+
type: 8p8c
|
|
1322
|
+
front-ports:
|
|
1323
|
+
- name: Front Port 1
|
|
1324
|
+
type: 8p8c
|
|
1325
|
+
rear_port: Rear Port 1
|
|
1326
|
+
- name: Front Port 2
|
|
1327
|
+
type: 8p8c
|
|
1328
|
+
rear_port: Rear Port 2
|
|
1329
|
+
- name: Front Port 3
|
|
1330
|
+
type: 8p8c
|
|
1331
|
+
rear_port: Rear Port 3
|
|
1332
|
+
module-bays:
|
|
1333
|
+
- name: Module Bay 1
|
|
1334
|
+
position: 1
|
|
1335
|
+
- name: Module Bay 2
|
|
1336
|
+
position: 2
|
|
1337
|
+
- name: Module Bay 3
|
|
1338
|
+
position: 3
|
|
1339
|
+
"""
|
|
1340
|
+
|
|
1341
|
+
# Add all required permissions to the test user
|
|
1342
|
+
self.add_permissions(
|
|
1343
|
+
"dcim.view_moduletype",
|
|
1344
|
+
"dcim.add_moduletype",
|
|
1345
|
+
"dcim.add_consoleporttemplate",
|
|
1346
|
+
"dcim.add_consoleserverporttemplate",
|
|
1347
|
+
"dcim.add_powerporttemplate",
|
|
1348
|
+
"dcim.add_poweroutlettemplate",
|
|
1349
|
+
"dcim.add_interfacetemplate",
|
|
1350
|
+
"dcim.add_frontporttemplate",
|
|
1351
|
+
"dcim.add_rearporttemplate",
|
|
1352
|
+
"dcim.add_modulebaytemplate",
|
|
1216
1353
|
)
|
|
1217
1354
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1355
|
+
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
1356
|
+
response = self.client.post(reverse("dcim:moduletype_import"), data=form_data, follow=True)
|
|
1357
|
+
self.assertHttpStatus(response, 200)
|
|
1358
|
+
mt = ModuleType.objects.get(model="TEST-1000")
|
|
1221
1359
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
"allocated_draw": 50,
|
|
1228
|
-
}
|
|
1360
|
+
# Verify all of the components were created
|
|
1361
|
+
self.assertEqual(mt.console_port_templates.count(), 3)
|
|
1362
|
+
cp1 = mt.console_port_templates.first()
|
|
1363
|
+
self.assertEqual(cp1.name, "Console Port 1")
|
|
1364
|
+
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
|
1229
1365
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
"maximum_draw": 100,
|
|
1235
|
-
"allocated_draw": 50,
|
|
1236
|
-
}
|
|
1366
|
+
self.assertEqual(mt.console_server_port_templates.count(), 3)
|
|
1367
|
+
csp1 = mt.console_server_port_templates.first()
|
|
1368
|
+
self.assertEqual(csp1.name, "Console Server Port 1")
|
|
1369
|
+
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
|
1237
1370
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1371
|
+
self.assertEqual(mt.power_port_templates.count(), 3)
|
|
1372
|
+
pp1 = mt.power_port_templates.first()
|
|
1373
|
+
self.assertEqual(pp1.name, "Power Port 1")
|
|
1374
|
+
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
|
1243
1375
|
|
|
1376
|
+
self.assertEqual(mt.power_outlet_templates.count(), 3)
|
|
1377
|
+
po1 = mt.power_outlet_templates.first()
|
|
1378
|
+
self.assertEqual(po1.name, "Power Outlet 1")
|
|
1379
|
+
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
|
1380
|
+
self.assertEqual(po1.power_port_template, pp1)
|
|
1381
|
+
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
|
1244
1382
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1383
|
+
self.assertEqual(mt.interface_templates.count(), 3)
|
|
1384
|
+
iface1 = mt.interface_templates.first()
|
|
1385
|
+
self.assertEqual(iface1.name, "Interface 1")
|
|
1386
|
+
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
|
1387
|
+
self.assertTrue(iface1.mgmt_only)
|
|
1247
1388
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1389
|
+
self.assertEqual(mt.rear_port_templates.count(), 3)
|
|
1390
|
+
rp1 = mt.rear_port_templates.first()
|
|
1391
|
+
self.assertEqual(rp1.name, "Rear Port 1")
|
|
1252
1392
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1393
|
+
self.assertEqual(mt.front_port_templates.count(), 3)
|
|
1394
|
+
fp1 = mt.front_port_templates.first()
|
|
1395
|
+
self.assertEqual(fp1.name, "Front Port 1")
|
|
1396
|
+
self.assertEqual(fp1.rear_port_template, rp1)
|
|
1397
|
+
self.assertEqual(fp1.rear_port_position, 1)
|
|
1256
1398
|
|
|
1257
|
-
|
|
1399
|
+
self.assertEqual(mt.module_bay_templates.count(), 3)
|
|
1400
|
+
mb1 = mt.module_bay_templates.first()
|
|
1401
|
+
self.assertEqual(mb1.name, "Module Bay 1")
|
|
1402
|
+
self.assertEqual(mb1.position, "1")
|
|
1258
1403
|
|
|
1259
|
-
|
|
1404
|
+
def test_import_objects_unknown_type_enums(self):
|
|
1405
|
+
"""
|
|
1406
|
+
YAML import of data with `type` values that we don't recognize should remap those to "other" rather than fail.
|
|
1407
|
+
"""
|
|
1408
|
+
manufacturer = Manufacturer.objects.first()
|
|
1409
|
+
IMPORT_DATA = f"""
|
|
1410
|
+
manufacturer: {manufacturer.name}
|
|
1411
|
+
model: TEST-2000
|
|
1412
|
+
console-ports:
|
|
1413
|
+
- name: Console Port Alpha-Beta
|
|
1414
|
+
type: alpha-beta
|
|
1415
|
+
console-server-ports:
|
|
1416
|
+
- name: Console Server Port Pineapple
|
|
1417
|
+
type: pineapple
|
|
1418
|
+
power-ports:
|
|
1419
|
+
- name: Power Port Fred
|
|
1420
|
+
type: frederick
|
|
1421
|
+
power-outlets:
|
|
1422
|
+
- name: Power Outlet Rick
|
|
1423
|
+
type: frederick
|
|
1424
|
+
power_port_template: Power Port Fred
|
|
1425
|
+
interfaces:
|
|
1426
|
+
- name: Interface North
|
|
1427
|
+
type: northern
|
|
1428
|
+
rear-ports:
|
|
1429
|
+
- name: Rear Port Foosball
|
|
1430
|
+
type: foosball
|
|
1431
|
+
front-ports:
|
|
1432
|
+
- name: Front Port Pickleball
|
|
1433
|
+
type: pickleball
|
|
1434
|
+
rear_port_template: Rear Port Foosball
|
|
1435
|
+
module-bays:
|
|
1436
|
+
- name: Module Bay 1
|
|
1437
|
+
position: 1
|
|
1438
|
+
- name: Module Bay 2
|
|
1439
|
+
position: 2
|
|
1440
|
+
- name: Module Bay 3
|
|
1441
|
+
position: 3
|
|
1442
|
+
"""
|
|
1443
|
+
# Add all required permissions to the test user
|
|
1444
|
+
self.add_permissions(
|
|
1445
|
+
"dcim.view_moduletype",
|
|
1446
|
+
"dcim.view_manufacturer",
|
|
1447
|
+
"dcim.add_moduletype",
|
|
1448
|
+
"dcim.add_consoleporttemplate",
|
|
1449
|
+
"dcim.add_consoleserverporttemplate",
|
|
1450
|
+
"dcim.add_powerporttemplate",
|
|
1451
|
+
"dcim.add_poweroutlettemplate",
|
|
1452
|
+
"dcim.add_interfacetemplate",
|
|
1453
|
+
"dcim.add_frontporttemplate",
|
|
1454
|
+
"dcim.add_rearporttemplate",
|
|
1455
|
+
"dcim.add_modulebaytemplate",
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
1459
|
+
response = self.client.post(reverse("dcim:moduletype_import"), data=form_data, follow=True)
|
|
1460
|
+
self.assertHttpStatus(response, 200)
|
|
1461
|
+
mt = ModuleType.objects.get(model="TEST-2000")
|
|
1462
|
+
|
|
1463
|
+
# Verify all of the components were created with appropriate "other" types
|
|
1464
|
+
self.assertEqual(mt.console_port_templates.count(), 1)
|
|
1465
|
+
cpt = ConsolePortTemplate.objects.filter(module_type=mt).first()
|
|
1466
|
+
self.assertEqual(cpt.name, "Console Port Alpha-Beta")
|
|
1467
|
+
self.assertEqual(cpt.type, ConsolePortTypeChoices.TYPE_OTHER)
|
|
1468
|
+
|
|
1469
|
+
self.assertEqual(mt.console_server_port_templates.count(), 1)
|
|
1470
|
+
cspt = ConsoleServerPortTemplate.objects.filter(module_type=mt).first()
|
|
1471
|
+
self.assertEqual(cspt.name, "Console Server Port Pineapple")
|
|
1472
|
+
self.assertEqual(cspt.type, ConsolePortTypeChoices.TYPE_OTHER)
|
|
1473
|
+
|
|
1474
|
+
self.assertEqual(mt.power_port_templates.count(), 1)
|
|
1475
|
+
ppt = PowerPortTemplate.objects.filter(module_type=mt).first()
|
|
1476
|
+
self.assertEqual(ppt.name, "Power Port Fred")
|
|
1477
|
+
self.assertEqual(ppt.type, PowerPortTypeChoices.TYPE_OTHER)
|
|
1478
|
+
|
|
1479
|
+
self.assertEqual(mt.power_outlet_templates.count(), 1)
|
|
1480
|
+
pot = PowerOutletTemplate.objects.filter(module_type=mt).first()
|
|
1481
|
+
self.assertEqual(pot.name, "Power Outlet Rick")
|
|
1482
|
+
self.assertEqual(pot.type, PowerOutletTypeChoices.TYPE_OTHER)
|
|
1483
|
+
self.assertEqual(pot.power_port_template, ppt)
|
|
1484
|
+
|
|
1485
|
+
self.assertEqual(mt.interface_templates.count(), 1)
|
|
1486
|
+
it = InterfaceTemplate.objects.filter(module_type=mt).first()
|
|
1487
|
+
self.assertEqual(it.name, "Interface North")
|
|
1488
|
+
self.assertEqual(it.type, InterfaceTypeChoices.TYPE_OTHER)
|
|
1489
|
+
|
|
1490
|
+
self.assertEqual(mt.rear_port_templates.count(), 1)
|
|
1491
|
+
rpt = RearPortTemplate.objects.filter(module_type=mt).first()
|
|
1492
|
+
self.assertEqual(rpt.name, "Rear Port Foosball")
|
|
1493
|
+
self.assertEqual(rpt.type, PortTypeChoices.TYPE_OTHER)
|
|
1494
|
+
|
|
1495
|
+
self.assertEqual(mt.front_port_templates.count(), 1)
|
|
1496
|
+
fpt = FrontPortTemplate.objects.filter(module_type=mt).first()
|
|
1497
|
+
self.assertEqual(fpt.name, "Front Port Pickleball")
|
|
1498
|
+
self.assertEqual(fpt.type, PortTypeChoices.TYPE_OTHER)
|
|
1499
|
+
|
|
1500
|
+
self.assertEqual(mt.module_bay_templates.count(), 3)
|
|
1501
|
+
# ModuleBayTemplate doesn't have a type field.
|
|
1502
|
+
mbt = ModuleBayTemplate.objects.filter(module_type=mt).first()
|
|
1503
|
+
self.assertEqual(mbt.position, "1")
|
|
1504
|
+
self.assertEqual(mbt.name, "Module Bay 1")
|
|
1505
|
+
|
|
1506
|
+
def test_moduletype_export(self):
|
|
1507
|
+
url = reverse("dcim:moduletype_list")
|
|
1508
|
+
self.add_permissions("dcim.view_moduletype")
|
|
1509
|
+
|
|
1510
|
+
response = self.client.get(f"{url}?export")
|
|
1511
|
+
self.assertEqual(response.status_code, 200)
|
|
1512
|
+
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
|
|
1513
|
+
module_types = ModuleType.objects.all()
|
|
1514
|
+
module_type = module_types.first()
|
|
1515
|
+
|
|
1516
|
+
self.assertEqual(len(data), module_types.count())
|
|
1517
|
+
self.assertEqual(data[0]["manufacturer"], module_type.manufacturer.name)
|
|
1518
|
+
self.assertEqual(data[0]["model"], module_type.model)
|
|
1519
|
+
|
|
1520
|
+
|
|
1521
|
+
#
|
|
1522
|
+
# DeviceType components
|
|
1523
|
+
#
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1527
|
+
model = ConsolePortTemplate
|
|
1528
|
+
|
|
1529
|
+
@classmethod
|
|
1530
|
+
def setUpTestData(cls):
|
|
1531
|
+
manufacturer = Manufacturer.objects.first()
|
|
1532
|
+
devicetypes = (
|
|
1533
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1534
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1535
|
+
)
|
|
1536
|
+
|
|
1537
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 1")
|
|
1538
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 2")
|
|
1539
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 3")
|
|
1540
|
+
|
|
1541
|
+
cls.form_data = {
|
|
1542
|
+
"device_type": devicetypes[1].pk,
|
|
1543
|
+
"name": "Console Port Template X",
|
|
1544
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
cls.bulk_create_data = {
|
|
1548
|
+
"device_type": devicetypes[1].pk,
|
|
1549
|
+
"name_pattern": "Console Port Template [4-6]",
|
|
1550
|
+
"description": "View Test Bulk Create Console Ports",
|
|
1551
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
cls.bulk_edit_data = {
|
|
1555
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
test_instance = cls.model.objects.first()
|
|
1559
|
+
cls.update_data = {
|
|
1560
|
+
"name": test_instance.name,
|
|
1561
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1562
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1563
|
+
"label": "new test label",
|
|
1564
|
+
"description": "new test description",
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1569
|
+
model = ConsoleServerPortTemplate
|
|
1570
|
+
|
|
1571
|
+
@classmethod
|
|
1572
|
+
def setUpTestData(cls):
|
|
1573
|
+
manufacturer = Manufacturer.objects.first()
|
|
1574
|
+
devicetypes = (
|
|
1575
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1576
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 1")
|
|
1580
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 2")
|
|
1581
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 3")
|
|
1582
|
+
|
|
1583
|
+
cls.form_data = {
|
|
1584
|
+
"device_type": devicetypes[1].pk,
|
|
1585
|
+
"name": "Console Server Port Template X",
|
|
1586
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
cls.bulk_create_data = {
|
|
1590
|
+
"device_type": devicetypes[1].pk,
|
|
1591
|
+
"name_pattern": "Console Server Port Template [4-6]",
|
|
1592
|
+
"description": "View Test Bulk Create Console Server Ports",
|
|
1593
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
cls.bulk_edit_data = {
|
|
1597
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
test_instance = cls.model.objects.first()
|
|
1601
|
+
cls.update_data = {
|
|
1602
|
+
"name": test_instance.name,
|
|
1603
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1604
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1605
|
+
"label": "new test label",
|
|
1606
|
+
"description": "new test description",
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1611
|
+
model = PowerPortTemplate
|
|
1612
|
+
|
|
1613
|
+
@classmethod
|
|
1614
|
+
def setUpTestData(cls):
|
|
1615
|
+
manufacturer = Manufacturer.objects.first()
|
|
1616
|
+
devicetypes = (
|
|
1617
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1618
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 1")
|
|
1622
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 2")
|
|
1623
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 3")
|
|
1624
|
+
|
|
1625
|
+
cls.form_data = {
|
|
1626
|
+
"device_type": devicetypes[1].pk,
|
|
1627
|
+
"name": "Power Port Template X",
|
|
1628
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1629
|
+
"maximum_draw": 100,
|
|
1630
|
+
"allocated_draw": 50,
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
cls.bulk_create_data = {
|
|
1634
|
+
"device_type": devicetypes[1].pk,
|
|
1635
|
+
"name_pattern": "Power Port Template [4-6]",
|
|
1636
|
+
"description": "View Test Bulk Create Power Ports",
|
|
1637
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1638
|
+
"maximum_draw": 100,
|
|
1639
|
+
"allocated_draw": 50,
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
cls.bulk_edit_data = {
|
|
1643
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1644
|
+
"maximum_draw": 100,
|
|
1645
|
+
"allocated_draw": 50,
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
test_instance = cls.model.objects.first()
|
|
1649
|
+
cls.update_data = {
|
|
1650
|
+
"name": test_instance.name,
|
|
1651
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1652
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1653
|
+
"label": "new test label",
|
|
1654
|
+
"description": "new test description",
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1659
|
+
model = PowerOutletTemplate
|
|
1660
|
+
|
|
1661
|
+
@classmethod
|
|
1662
|
+
def setUpTestData(cls):
|
|
1663
|
+
manufacturer = Manufacturer.objects.first()
|
|
1664
|
+
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1665
|
+
|
|
1666
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 1")
|
|
1667
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 2")
|
|
1668
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 3")
|
|
1669
|
+
|
|
1670
|
+
powerports = (PowerPortTemplate.objects.create(device_type=devicetype, name="Power Port Template 1"),)
|
|
1671
|
+
|
|
1672
|
+
cls.form_data = {
|
|
1260
1673
|
"device_type": devicetype.pk,
|
|
1261
1674
|
"name": "Power Outlet Template X",
|
|
1262
1675
|
"type": PowerOutletTypeChoices.TYPE_IEC_C13,
|
|
@@ -1267,6 +1680,7 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|
|
1267
1680
|
cls.bulk_create_data = {
|
|
1268
1681
|
"device_type": devicetype.pk,
|
|
1269
1682
|
"name_pattern": "Power Outlet Template [4-6]",
|
|
1683
|
+
"description": "View Test Bulk Create Power Outlets",
|
|
1270
1684
|
"type": PowerOutletTypeChoices.TYPE_IEC_C13,
|
|
1271
1685
|
"power_port_template": powerports[0].pk,
|
|
1272
1686
|
"feed_leg": PowerOutletFeedLegChoices.FEED_LEG_B,
|
|
@@ -1277,6 +1691,17 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|
|
1277
1691
|
"feed_leg": PowerOutletFeedLegChoices.FEED_LEG_B,
|
|
1278
1692
|
}
|
|
1279
1693
|
|
|
1694
|
+
test_instance = cls.model.objects.first()
|
|
1695
|
+
cls.update_data = {
|
|
1696
|
+
"name": test_instance.name,
|
|
1697
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1698
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1699
|
+
# power_port_template must match the parent device/module type
|
|
1700
|
+
"power_port_template": getattr(test_instance.power_port_template, "pk", None),
|
|
1701
|
+
"label": "new test label",
|
|
1702
|
+
"description": "new test description",
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1280
1705
|
|
|
1281
1706
|
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1282
1707
|
model = InterfaceTemplate
|
|
@@ -1289,9 +1714,21 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1289
1714
|
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1290
1715
|
)
|
|
1291
1716
|
|
|
1292
|
-
InterfaceTemplate.objects.create(
|
|
1293
|
-
|
|
1294
|
-
|
|
1717
|
+
InterfaceTemplate.objects.create(
|
|
1718
|
+
device_type=devicetypes[0],
|
|
1719
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1720
|
+
name="Interface Template 1",
|
|
1721
|
+
)
|
|
1722
|
+
InterfaceTemplate.objects.create(
|
|
1723
|
+
device_type=devicetypes[0],
|
|
1724
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1725
|
+
name="Interface Template 2",
|
|
1726
|
+
)
|
|
1727
|
+
InterfaceTemplate.objects.create(
|
|
1728
|
+
device_type=devicetypes[0],
|
|
1729
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1730
|
+
name="Interface Template 3",
|
|
1731
|
+
)
|
|
1295
1732
|
|
|
1296
1733
|
cls.form_data = {
|
|
1297
1734
|
"device_type": devicetypes[1].pk,
|
|
@@ -1305,6 +1742,7 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1305
1742
|
"name_pattern": "Interface Template [4-6]",
|
|
1306
1743
|
# Test that a label can be applied to each generated interface templates
|
|
1307
1744
|
"label_pattern": "Interface Template Label [3-5]",
|
|
1745
|
+
"description": "View Test Bulk Create Interfaces",
|
|
1308
1746
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
1309
1747
|
"mgmt_only": True,
|
|
1310
1748
|
}
|
|
@@ -1314,6 +1752,16 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1314
1752
|
"mgmt_only": True,
|
|
1315
1753
|
}
|
|
1316
1754
|
|
|
1755
|
+
test_instance = cls.model.objects.first()
|
|
1756
|
+
cls.update_data = {
|
|
1757
|
+
"name": test_instance.name,
|
|
1758
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1759
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1760
|
+
"type": test_instance.type,
|
|
1761
|
+
"label": "new test label",
|
|
1762
|
+
"description": "new test description",
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1317
1765
|
|
|
1318
1766
|
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1319
1767
|
model = FrontPortTemplate
|
|
@@ -1324,29 +1772,62 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1324
1772
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1325
1773
|
|
|
1326
1774
|
rearports = (
|
|
1327
|
-
RearPortTemplate.objects.create(
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1775
|
+
RearPortTemplate.objects.create(
|
|
1776
|
+
device_type=devicetype,
|
|
1777
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1778
|
+
positions=24,
|
|
1779
|
+
name="Rear Port Template 1",
|
|
1780
|
+
),
|
|
1781
|
+
RearPortTemplate.objects.create(
|
|
1782
|
+
device_type=devicetype,
|
|
1783
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1784
|
+
positions=24,
|
|
1785
|
+
name="Rear Port Template 2",
|
|
1786
|
+
),
|
|
1787
|
+
RearPortTemplate.objects.create(
|
|
1788
|
+
device_type=devicetype,
|
|
1789
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1790
|
+
positions=24,
|
|
1791
|
+
name="Rear Port Template 3",
|
|
1792
|
+
),
|
|
1793
|
+
RearPortTemplate.objects.create(
|
|
1794
|
+
device_type=devicetype,
|
|
1795
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1796
|
+
positions=24,
|
|
1797
|
+
name="Rear Port Template 4",
|
|
1798
|
+
),
|
|
1799
|
+
RearPortTemplate.objects.create(
|
|
1800
|
+
device_type=devicetype,
|
|
1801
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1802
|
+
positions=24,
|
|
1803
|
+
name="Rear Port Template 5",
|
|
1804
|
+
),
|
|
1805
|
+
RearPortTemplate.objects.create(
|
|
1806
|
+
device_type=devicetype,
|
|
1807
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1808
|
+
positions=24,
|
|
1809
|
+
name="Rear Port Template 6",
|
|
1810
|
+
),
|
|
1333
1811
|
)
|
|
1334
1812
|
|
|
1335
1813
|
FrontPortTemplate.objects.create(
|
|
1336
1814
|
device_type=devicetype,
|
|
1337
|
-
name="Front Port Template 1",
|
|
1815
|
+
name="View Test Front Port Template 1",
|
|
1816
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1338
1817
|
rear_port_template=rearports[0],
|
|
1339
1818
|
rear_port_position=1,
|
|
1340
1819
|
)
|
|
1341
1820
|
FrontPortTemplate.objects.create(
|
|
1342
1821
|
device_type=devicetype,
|
|
1343
|
-
name="Front Port Template 2",
|
|
1822
|
+
name="View Test Front Port Template 2",
|
|
1823
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1344
1824
|
rear_port_template=rearports[1],
|
|
1345
1825
|
rear_port_position=1,
|
|
1346
1826
|
)
|
|
1347
1827
|
FrontPortTemplate.objects.create(
|
|
1348
1828
|
device_type=devicetype,
|
|
1349
|
-
name="Front Port Template 3",
|
|
1829
|
+
name="View Test Front Port Template 3",
|
|
1830
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1350
1831
|
rear_port_template=rearports[2],
|
|
1351
1832
|
rear_port_position=1,
|
|
1352
1833
|
)
|
|
@@ -1361,13 +1842,26 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1361
1842
|
|
|
1362
1843
|
cls.bulk_create_data = {
|
|
1363
1844
|
"device_type": devicetype.pk,
|
|
1364
|
-
"name_pattern": "Front Port [4-6]",
|
|
1845
|
+
"name_pattern": "View Test Front Port [4-6]",
|
|
1846
|
+
"description": "View Test Bulk Create Front Ports",
|
|
1365
1847
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1366
1848
|
"rear_port_template_set": [f"{rp.pk}:1" for rp in rearports[3:6]],
|
|
1367
1849
|
}
|
|
1368
1850
|
|
|
1369
1851
|
cls.bulk_edit_data = {
|
|
1370
|
-
"type": PortTypeChoices.
|
|
1852
|
+
"type": PortTypeChoices.TYPE_4P4C,
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
test_instance = cls.model.objects.first()
|
|
1856
|
+
cls.update_data = {
|
|
1857
|
+
"name": test_instance.name,
|
|
1858
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1859
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1860
|
+
"rear_port_template": test_instance.rear_port_template.pk,
|
|
1861
|
+
"rear_port_position": test_instance.rear_port_position,
|
|
1862
|
+
"type": test_instance.type,
|
|
1863
|
+
"label": "new test label",
|
|
1864
|
+
"description": "new test description",
|
|
1371
1865
|
}
|
|
1372
1866
|
|
|
1373
1867
|
|
|
@@ -1382,9 +1876,24 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1382
1876
|
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1383
1877
|
)
|
|
1384
1878
|
|
|
1385
|
-
RearPortTemplate.objects.create(
|
|
1386
|
-
|
|
1387
|
-
|
|
1879
|
+
RearPortTemplate.objects.create(
|
|
1880
|
+
device_type=devicetypes[0],
|
|
1881
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1882
|
+
positions=24,
|
|
1883
|
+
name="Rear Port Template 1",
|
|
1884
|
+
)
|
|
1885
|
+
RearPortTemplate.objects.create(
|
|
1886
|
+
device_type=devicetypes[0],
|
|
1887
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1888
|
+
positions=24,
|
|
1889
|
+
name="Rear Port Template 2",
|
|
1890
|
+
)
|
|
1891
|
+
RearPortTemplate.objects.create(
|
|
1892
|
+
device_type=devicetypes[0],
|
|
1893
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1894
|
+
positions=24,
|
|
1895
|
+
name="Rear Port Template 3",
|
|
1896
|
+
)
|
|
1388
1897
|
|
|
1389
1898
|
cls.form_data = {
|
|
1390
1899
|
"device_type": devicetypes[1].pk,
|
|
@@ -1396,6 +1905,7 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1396
1905
|
cls.bulk_create_data = {
|
|
1397
1906
|
"device_type": devicetypes[1].pk,
|
|
1398
1907
|
"name_pattern": "Rear Port Template [4-6]",
|
|
1908
|
+
"description": "View Test Bulk Create Rear Ports",
|
|
1399
1909
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1400
1910
|
"positions": 2,
|
|
1401
1911
|
}
|
|
@@ -1404,6 +1914,17 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1404
1914
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1405
1915
|
}
|
|
1406
1916
|
|
|
1917
|
+
test_instance = cls.model.objects.first()
|
|
1918
|
+
cls.update_data = {
|
|
1919
|
+
"name": test_instance.name,
|
|
1920
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1921
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1922
|
+
"positions": test_instance.positions,
|
|
1923
|
+
"type": test_instance.type,
|
|
1924
|
+
"label": "new test label",
|
|
1925
|
+
"description": "new test description",
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1407
1928
|
|
|
1408
1929
|
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1409
1930
|
model = DeviceBayTemplate
|
|
@@ -1436,31 +1957,80 @@ class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1436
1957
|
cls.bulk_create_data = {
|
|
1437
1958
|
"device_type": devicetypes[1].pk,
|
|
1438
1959
|
"name_pattern": "Device Bay Template [4-6]",
|
|
1960
|
+
"description": "View Test Bulk Create Device Bays",
|
|
1439
1961
|
}
|
|
1440
1962
|
|
|
1441
1963
|
cls.bulk_edit_data = {
|
|
1442
1964
|
"description": "Foo bar",
|
|
1443
1965
|
}
|
|
1444
1966
|
|
|
1967
|
+
test_instance = cls.model.objects.first()
|
|
1968
|
+
cls.update_data = {
|
|
1969
|
+
"name": test_instance.name,
|
|
1970
|
+
"device_type": test_instance.device_type.pk,
|
|
1971
|
+
"label": "new test label",
|
|
1972
|
+
"description": "new test description",
|
|
1973
|
+
}
|
|
1445
1974
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1975
|
+
|
|
1976
|
+
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1977
|
+
model = ModuleBayTemplate
|
|
1448
1978
|
|
|
1449
1979
|
@classmethod
|
|
1450
1980
|
def setUpTestData(cls):
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
# Protected FK to SoftwareImageFile prevents deletion
|
|
1454
|
-
DeviceTypeToSoftwareImageFile.objects.all().delete()
|
|
1455
|
-
# Protected FK to SoftwareVersion prevents deletion
|
|
1456
|
-
Device.objects.all().update(software_version=None)
|
|
1981
|
+
device_type = DeviceType.objects.first()
|
|
1982
|
+
module_type = ModuleType.objects.first()
|
|
1457
1983
|
|
|
1458
1984
|
cls.form_data = {
|
|
1459
|
-
"
|
|
1460
|
-
"
|
|
1461
|
-
"
|
|
1462
|
-
"
|
|
1463
|
-
"
|
|
1985
|
+
"device_type": device_type.pk,
|
|
1986
|
+
"module_type": None,
|
|
1987
|
+
"name": "Module Bay Template X",
|
|
1988
|
+
"position": "Test modulebaytemplate position",
|
|
1989
|
+
"description": "Test modulebaytemplate description",
|
|
1990
|
+
"label": "Test modulebaytemplate label",
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
cls.bulk_create_data = {
|
|
1994
|
+
"module_type": module_type.pk,
|
|
1995
|
+
"name_pattern": "Test Module Bay Template [5-7]",
|
|
1996
|
+
"position_pattern": "Test Module Bay Template Position [10-12]",
|
|
1997
|
+
"label_pattern": "Test modulebaytemplate label [1-3]",
|
|
1998
|
+
"description": "Test modulebaytemplate description",
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
cls.bulk_edit_data = {
|
|
2002
|
+
"description": "Description changed",
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
test_instance = cls.model.objects.first()
|
|
2006
|
+
cls.update_data = {
|
|
2007
|
+
"name": test_instance.name,
|
|
2008
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
2009
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
2010
|
+
"position": "new test position",
|
|
2011
|
+
"label": "new test label",
|
|
2012
|
+
"description": "new test description",
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
|
|
2016
|
+
class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
2017
|
+
model = Platform
|
|
2018
|
+
|
|
2019
|
+
@classmethod
|
|
2020
|
+
def setUpTestData(cls):
|
|
2021
|
+
manufacturer = Manufacturer.objects.first()
|
|
2022
|
+
|
|
2023
|
+
# Protected FK to SoftwareImageFile prevents deletion
|
|
2024
|
+
DeviceTypeToSoftwareImageFile.objects.all().delete()
|
|
2025
|
+
# Protected FK to SoftwareVersion prevents deletion
|
|
2026
|
+
Device.objects.all().update(software_version=None)
|
|
2027
|
+
|
|
2028
|
+
cls.form_data = {
|
|
2029
|
+
"name": "Platform X",
|
|
2030
|
+
"manufacturer": manufacturer.pk,
|
|
2031
|
+
"napalm_driver": "junos",
|
|
2032
|
+
"napalm_args": None,
|
|
2033
|
+
"network_driver": "juniper_junos",
|
|
1464
2034
|
"description": "A new platform",
|
|
1465
2035
|
}
|
|
1466
2036
|
|
|
@@ -1593,11 +2163,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1593
2163
|
)
|
|
1594
2164
|
|
|
1595
2165
|
intf_status = Status.objects.get_for_model(Interface).first()
|
|
1596
|
-
|
|
2166
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
1597
2167
|
cls.interfaces = (
|
|
1598
|
-
Interface.objects.create(device=devices[0], name="Interface
|
|
1599
|
-
Interface.objects.create(device=devices[0], name="Interface
|
|
1600
|
-
Interface.objects.create(device=devices[0], name="Interface
|
|
2168
|
+
Interface.objects.create(device=devices[0], name="Interface A1", status=intf_status, role=intf_role),
|
|
2169
|
+
Interface.objects.create(device=devices[0], name="Interface A2", status=intf_status),
|
|
2170
|
+
Interface.objects.create(device=devices[0], name="Interface A3", status=intf_status, role=intf_role),
|
|
1601
2171
|
)
|
|
1602
2172
|
|
|
1603
2173
|
for device, ipaddress in zip(devices, ipaddresses):
|
|
@@ -1651,55 +2221,446 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1651
2221
|
}
|
|
1652
2222
|
|
|
1653
2223
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1654
|
-
def test_device_consoleports(self):
|
|
1655
|
-
device = Device.objects.first()
|
|
2224
|
+
def test_device_consoleports(self):
|
|
2225
|
+
device = Device.objects.first()
|
|
2226
|
+
|
|
2227
|
+
ConsolePort.objects.create(device=device, name="Console Port 1")
|
|
2228
|
+
ConsolePort.objects.create(device=device, name="Console Port 2")
|
|
2229
|
+
ConsolePort.objects.create(device=device, name="Console Port 3")
|
|
2230
|
+
|
|
2231
|
+
url = reverse("dcim:device_consoleports", kwargs={"pk": device.pk})
|
|
2232
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2233
|
+
|
|
2234
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2235
|
+
def test_device_consoleserverports(self):
|
|
2236
|
+
device = Device.objects.first()
|
|
2237
|
+
|
|
2238
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 1")
|
|
2239
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 2")
|
|
2240
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 3")
|
|
2241
|
+
|
|
2242
|
+
url = reverse("dcim:device_consoleserverports", kwargs={"pk": device.pk})
|
|
2243
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2244
|
+
|
|
2245
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2246
|
+
def test_device_powerports(self):
|
|
2247
|
+
device = Device.objects.first()
|
|
2248
|
+
|
|
2249
|
+
PowerPort.objects.create(device=device, name="Power Port 1")
|
|
2250
|
+
PowerPort.objects.create(device=device, name="Power Port 2")
|
|
2251
|
+
PowerPort.objects.create(device=device, name="Power Port 3")
|
|
2252
|
+
|
|
2253
|
+
url = reverse("dcim:device_powerports", kwargs={"pk": device.pk})
|
|
2254
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2255
|
+
|
|
2256
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2257
|
+
def test_device_poweroutlets(self):
|
|
2258
|
+
device = Device.objects.first()
|
|
2259
|
+
|
|
2260
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 1")
|
|
2261
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 2")
|
|
2262
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 3")
|
|
2263
|
+
|
|
2264
|
+
url = reverse("dcim:device_poweroutlets", kwargs={"pk": device.pk})
|
|
2265
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2266
|
+
|
|
2267
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2268
|
+
def test_device_interfaces(self):
|
|
2269
|
+
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
2270
|
+
self.add_permissions("ipam.add_ipaddress", "dcim.change_interface")
|
|
2271
|
+
|
|
2272
|
+
url = reverse("dcim:device_interfaces", kwargs={"pk": device.pk})
|
|
2273
|
+
response = self.client.get(url)
|
|
2274
|
+
self.assertHttpStatus(response, 200)
|
|
2275
|
+
response_body = response.content.decode(response.charset)
|
|
2276
|
+
# Count the number of occurrences of "Add IP address" in the response_body
|
|
2277
|
+
count = response_body.count("Add IP address")
|
|
2278
|
+
# Assert that "Add IP address" appears for each of the three interfaces
|
|
2279
|
+
self.assertEqual(count, 3)
|
|
2280
|
+
|
|
2281
|
+
def test_device_interface_assign_ipaddress(self):
|
|
2282
|
+
device = Device.objects.first()
|
|
2283
|
+
self.add_permissions(
|
|
2284
|
+
"ipam.add_ipaddress",
|
|
2285
|
+
"extras.view_status",
|
|
2286
|
+
"ipam.view_namespace",
|
|
2287
|
+
"dcim.view_device",
|
|
2288
|
+
"dcim.view_interface",
|
|
2289
|
+
)
|
|
2290
|
+
device_list_url = reverse("dcim:device_interfaces", args=(device.pk,))
|
|
2291
|
+
namespace = Namespace.objects.first()
|
|
2292
|
+
ipaddresses = [str(ipadress) for ipadress in IPAddress.objects.values_list("pk", flat=True)[:3]]
|
|
2293
|
+
add_new_ip_form_data = {
|
|
2294
|
+
"namespace": namespace.pk,
|
|
2295
|
+
"address": "1.1.1.7/24",
|
|
2296
|
+
"tenant": None,
|
|
2297
|
+
"status": Status.objects.get_for_model(IPAddress).first().pk,
|
|
2298
|
+
"type": IPAddressTypeChoices.TYPE_DHCP,
|
|
2299
|
+
"role": None,
|
|
2300
|
+
"nat_inside": None,
|
|
2301
|
+
"dns_name": None,
|
|
2302
|
+
"description": None,
|
|
2303
|
+
"tags": [],
|
|
2304
|
+
"interface": self.interfaces[0].id,
|
|
2305
|
+
}
|
|
2306
|
+
add_new_ip_request = {
|
|
2307
|
+
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={device_list_url}",
|
|
2308
|
+
"data": post_data(add_new_ip_form_data),
|
|
2309
|
+
}
|
|
2310
|
+
assign_ip_form_data = {"pk": ipaddresses}
|
|
2311
|
+
assign_ip_request = {
|
|
2312
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
2313
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
2314
|
+
"data": post_data(assign_ip_form_data),
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
with self.subTest("Assert Cannnot assign IPAddress('Add New') without permission"):
|
|
2318
|
+
# Assert Add new IPAddress
|
|
2319
|
+
response = self.client.post(**add_new_ip_request, follow=True)
|
|
2320
|
+
response_body = response.content.decode(response.charset)
|
|
2321
|
+
self.assertHttpStatus(response, 200)
|
|
2322
|
+
self.interfaces[0].refresh_from_db()
|
|
2323
|
+
self.assertEqual(self.interfaces[0].ip_addresses.all().count(), 0)
|
|
2324
|
+
self.assertIn(
|
|
2325
|
+
f"Interface with id "{self.interfaces[0].pk}" not found",
|
|
2326
|
+
response_body,
|
|
2327
|
+
)
|
|
2328
|
+
|
|
2329
|
+
with self.subTest("Assert Cannnot assign IPAddress(Exsisting IP) without permission"):
|
|
2330
|
+
# Assert Assign Exsisting IPAddress
|
|
2331
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
2332
|
+
response_body = response.content.decode(response.charset)
|
|
2333
|
+
self.assertHttpStatus(response, 200)
|
|
2334
|
+
self.interfaces[1].refresh_from_db()
|
|
2335
|
+
self.assertEqual(self.interfaces[1].ip_addresses.all().count(), 0)
|
|
2336
|
+
self.assertIn(
|
|
2337
|
+
f"Interface with id "{self.interfaces[1].pk}" not found",
|
|
2338
|
+
response_body,
|
|
2339
|
+
)
|
|
2340
|
+
|
|
2341
|
+
self.add_permissions("dcim.change_interface", "ipam.view_ipaddress")
|
|
2342
|
+
|
|
2343
|
+
with self.subTest("Assert Create and Assign IPAddress"):
|
|
2344
|
+
self.assertHttpStatus(self.client.post(**add_new_ip_request), 302)
|
|
2345
|
+
self.interfaces[0].refresh_from_db()
|
|
2346
|
+
self.assertEqual(
|
|
2347
|
+
str(self.interfaces[0].ip_addresses.all().first().address),
|
|
2348
|
+
add_new_ip_form_data["address"],
|
|
2349
|
+
)
|
|
2350
|
+
|
|
2351
|
+
with self.subTest("Assert Assign IPAddress"):
|
|
2352
|
+
response = self.client.post(**assign_ip_request)
|
|
2353
|
+
self.assertHttpStatus(response, 302)
|
|
2354
|
+
self.interfaces[1].refresh_from_db()
|
|
2355
|
+
self.assertEqual(self.interfaces[1].ip_addresses.count(), 3)
|
|
2356
|
+
interface_ips = [str(ip) for ip in self.interfaces[1].ip_addresses.values_list("pk", flat=True)]
|
|
2357
|
+
self.assertEqual(
|
|
2358
|
+
sorted(ipaddresses),
|
|
2359
|
+
sorted(interface_ips),
|
|
2360
|
+
)
|
|
2361
|
+
|
|
2362
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2363
|
+
def test_device_rearports(self):
|
|
2364
|
+
device = Device.objects.first()
|
|
2365
|
+
|
|
2366
|
+
RearPort.objects.create(device=device, name="Rear Port 1")
|
|
2367
|
+
RearPort.objects.create(device=device, name="Rear Port 2")
|
|
2368
|
+
RearPort.objects.create(device=device, name="Rear Port 3")
|
|
2369
|
+
|
|
2370
|
+
url = reverse("dcim:device_rearports", kwargs={"pk": device.pk})
|
|
2371
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2372
|
+
|
|
2373
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2374
|
+
def test_device_frontports(self):
|
|
2375
|
+
device = Device.objects.first()
|
|
2376
|
+
rear_ports = (
|
|
2377
|
+
RearPort.objects.create(device=device, name="Rear Port 1"),
|
|
2378
|
+
RearPort.objects.create(device=device, name="Rear Port 2"),
|
|
2379
|
+
RearPort.objects.create(device=device, name="Rear Port 3"),
|
|
2380
|
+
)
|
|
2381
|
+
|
|
2382
|
+
FrontPort.objects.create(
|
|
2383
|
+
device=device,
|
|
2384
|
+
name="Front Port 1",
|
|
2385
|
+
rear_port=rear_ports[0],
|
|
2386
|
+
rear_port_position=1,
|
|
2387
|
+
)
|
|
2388
|
+
FrontPort.objects.create(
|
|
2389
|
+
device=device,
|
|
2390
|
+
name="Front Port 2",
|
|
2391
|
+
rear_port=rear_ports[1],
|
|
2392
|
+
rear_port_position=1,
|
|
2393
|
+
)
|
|
2394
|
+
FrontPort.objects.create(
|
|
2395
|
+
device=device,
|
|
2396
|
+
name="Front Port 3",
|
|
2397
|
+
rear_port=rear_ports[2],
|
|
2398
|
+
rear_port_position=1,
|
|
2399
|
+
)
|
|
2400
|
+
|
|
2401
|
+
url = reverse("dcim:device_frontports", kwargs={"pk": device.pk})
|
|
2402
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2403
|
+
|
|
2404
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2405
|
+
def test_device_devicebays(self):
|
|
2406
|
+
device = Device.objects.first()
|
|
2407
|
+
|
|
2408
|
+
# Device Bay 1 was already created in setUpTestData()
|
|
2409
|
+
DeviceBay.objects.create(device=device, name="Device Bay 2")
|
|
2410
|
+
DeviceBay.objects.create(device=device, name="Device Bay 3")
|
|
2411
|
+
|
|
2412
|
+
url = reverse("dcim:device_devicebays", kwargs={"pk": device.pk})
|
|
2413
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2414
|
+
|
|
2415
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2416
|
+
def test_device_inventory(self):
|
|
2417
|
+
device = Device.objects.first()
|
|
2418
|
+
|
|
2419
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 1")
|
|
2420
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 2")
|
|
2421
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 3")
|
|
2422
|
+
|
|
2423
|
+
url = reverse("dcim:device_inventory", kwargs={"pk": device.pk})
|
|
2424
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2425
|
+
|
|
2426
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2427
|
+
def test_device_primary_ips(self):
|
|
2428
|
+
"""Test assigning a primary IP to a device."""
|
|
2429
|
+
self.add_permissions("dcim.change_device")
|
|
2430
|
+
|
|
2431
|
+
# Create an interface and assign an IP to it.
|
|
2432
|
+
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
2433
|
+
interface = device.interfaces.first()
|
|
2434
|
+
namespace = Namespace.objects.first()
|
|
2435
|
+
Prefix.objects.create(prefix="1.2.3.0/24", namespace=namespace, status=self.prefix_status)
|
|
2436
|
+
ip_address = IPAddress.objects.create(address="1.2.3.4/32", namespace=namespace, status=self.ipaddr_status)
|
|
2437
|
+
interface.ip_addresses.add(ip_address)
|
|
2438
|
+
|
|
2439
|
+
# Dupe the form data and populated primary_ip4 w/ ip_address
|
|
2440
|
+
form_data = self.form_data.copy()
|
|
2441
|
+
form_data["primary_ip4"] = ip_address.pk
|
|
2442
|
+
# Assert that update succeeds.
|
|
2443
|
+
request = {
|
|
2444
|
+
"path": self._get_url("edit", device),
|
|
2445
|
+
"data": post_data(form_data),
|
|
2446
|
+
}
|
|
2447
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
2448
|
+
self.assertInstanceEqual(self._get_queryset().order_by("last_updated").last(), form_data)
|
|
2449
|
+
|
|
2450
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2451
|
+
def test_local_config_context_schema_validation_pass(self):
|
|
2452
|
+
"""
|
|
2453
|
+
Given a config context schema
|
|
2454
|
+
And a device with local context that conforms to that schema
|
|
2455
|
+
Assert that the local context passes schema validation via full_clean()
|
|
2456
|
+
"""
|
|
2457
|
+
schema = ConfigContextSchema.objects.create(
|
|
2458
|
+
name="Schema 1",
|
|
2459
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
2460
|
+
)
|
|
2461
|
+
self.add_permissions("dcim.add_device")
|
|
2462
|
+
|
|
2463
|
+
form_data = self.form_data.copy()
|
|
2464
|
+
form_data["local_config_context_schema"] = schema.pk
|
|
2465
|
+
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
2466
|
+
|
|
2467
|
+
# Try POST with model-level permission
|
|
2468
|
+
request = {
|
|
2469
|
+
"path": self._get_url("add"),
|
|
2470
|
+
"data": post_data(form_data),
|
|
2471
|
+
}
|
|
2472
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
2473
|
+
self.assertEqual(
|
|
2474
|
+
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
2475
|
+
schema.pk,
|
|
2476
|
+
)
|
|
2477
|
+
|
|
2478
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2479
|
+
def test_local_config_context_schema_validation_fails(self):
|
|
2480
|
+
"""
|
|
2481
|
+
Given a config context schema
|
|
2482
|
+
And a device with local context that *does not* conform to that schema
|
|
2483
|
+
Assert that the local context fails schema validation via full_clean()
|
|
2484
|
+
"""
|
|
2485
|
+
schema = ConfigContextSchema.objects.create(
|
|
2486
|
+
name="Schema 1",
|
|
2487
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
2488
|
+
)
|
|
2489
|
+
self.add_permissions("dcim.add_device")
|
|
2490
|
+
|
|
2491
|
+
form_data = self.form_data.copy()
|
|
2492
|
+
form_data["local_config_context_schema"] = schema.pk
|
|
2493
|
+
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
2494
|
+
|
|
2495
|
+
# Try POST with model-level permission
|
|
2496
|
+
request = {
|
|
2497
|
+
"path": self._get_url("add"),
|
|
2498
|
+
"data": post_data(form_data),
|
|
2499
|
+
}
|
|
2500
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
2501
|
+
self.assertEqual(self._get_queryset().filter(name="Device X").count(), 0)
|
|
2502
|
+
|
|
2503
|
+
|
|
2504
|
+
class ModuleTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
2505
|
+
model = Module
|
|
2506
|
+
|
|
2507
|
+
@classmethod
|
|
2508
|
+
def setUpTestData(cls):
|
|
2509
|
+
Module.objects.all().delete()
|
|
2510
|
+
locations = Location.objects.filter(location_type=LocationType.objects.get(name="Campus"))[:2]
|
|
2511
|
+
manufacturer = Manufacturer.objects.first()
|
|
2512
|
+
|
|
2513
|
+
moduletypes = (
|
|
2514
|
+
ModuleType.objects.create(model="Module Type 1", manufacturer=manufacturer),
|
|
2515
|
+
ModuleType.objects.create(model="Module Type 2", manufacturer=manufacturer),
|
|
2516
|
+
)
|
|
2517
|
+
|
|
2518
|
+
moduleroles = Role.objects.get_for_model(Module)[:2]
|
|
2519
|
+
|
|
2520
|
+
statuses = Status.objects.get_for_model(Module)
|
|
2521
|
+
status_active = statuses[0]
|
|
2522
|
+
|
|
2523
|
+
cls.custom_fields = (
|
|
2524
|
+
CustomField.objects.create(
|
|
2525
|
+
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
2526
|
+
label="Crash Counter",
|
|
2527
|
+
default=0,
|
|
2528
|
+
),
|
|
2529
|
+
)
|
|
2530
|
+
cls.custom_fields[0].content_types.set([ContentType.objects.get_for_model(Module)])
|
|
2531
|
+
|
|
2532
|
+
modules = (
|
|
2533
|
+
Module.objects.create(
|
|
2534
|
+
location=locations[0],
|
|
2535
|
+
module_type=moduletypes[0],
|
|
2536
|
+
role=moduleroles[0],
|
|
2537
|
+
status=status_active,
|
|
2538
|
+
_custom_field_data={"crash_counter": 5},
|
|
2539
|
+
),
|
|
2540
|
+
Module.objects.create(
|
|
2541
|
+
location=locations[0],
|
|
2542
|
+
module_type=moduletypes[0],
|
|
2543
|
+
role=moduleroles[0],
|
|
2544
|
+
status=status_active,
|
|
2545
|
+
_custom_field_data={"crash_counter": 10},
|
|
2546
|
+
),
|
|
2547
|
+
Module.objects.create(
|
|
2548
|
+
location=locations[0],
|
|
2549
|
+
module_type=moduletypes[0],
|
|
2550
|
+
role=moduleroles[0],
|
|
2551
|
+
status=status_active,
|
|
2552
|
+
_custom_field_data={"crash_counter": 15},
|
|
2553
|
+
),
|
|
2554
|
+
)
|
|
2555
|
+
|
|
2556
|
+
cls.relationships = (
|
|
2557
|
+
Relationship(
|
|
2558
|
+
label="BGP Router-ID",
|
|
2559
|
+
key="router_id",
|
|
2560
|
+
type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
|
|
2561
|
+
source_type=ContentType.objects.get_for_model(Module),
|
|
2562
|
+
source_label="BGP Router ID",
|
|
2563
|
+
destination_type=ContentType.objects.get_for_model(IPAddress),
|
|
2564
|
+
destination_label="Module using this as BGP router-ID",
|
|
2565
|
+
),
|
|
2566
|
+
)
|
|
2567
|
+
for relationship in cls.relationships:
|
|
2568
|
+
relationship.validated_save()
|
|
2569
|
+
|
|
2570
|
+
cls.ipaddr_status = Status.objects.get_for_model(IPAddress).first()
|
|
2571
|
+
cls.prefix_status = Status.objects.get_for_model(Prefix).first()
|
|
2572
|
+
namespace = Namespace.objects.first()
|
|
2573
|
+
Prefix.objects.create(prefix="1.1.1.1/24", namespace=namespace, status=cls.prefix_status)
|
|
2574
|
+
Prefix.objects.create(prefix="2.2.2.2/24", namespace=namespace, status=cls.prefix_status)
|
|
2575
|
+
Prefix.objects.create(prefix="3.3.3.3/24", namespace=namespace, status=cls.prefix_status)
|
|
2576
|
+
ipaddresses = (
|
|
2577
|
+
IPAddress.objects.create(address="1.1.1.1/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2578
|
+
IPAddress.objects.create(address="2.2.2.2/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2579
|
+
IPAddress.objects.create(address="3.3.3.3/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2580
|
+
)
|
|
2581
|
+
|
|
2582
|
+
intf_status = Status.objects.get_for_model(Interface).first()
|
|
2583
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
2584
|
+
cls.interfaces = (
|
|
2585
|
+
Interface.objects.create(module=modules[0], name="Interface A1", status=intf_status, role=intf_role),
|
|
2586
|
+
Interface.objects.create(module=modules[0], name="Interface A2", status=intf_status),
|
|
2587
|
+
Interface.objects.create(module=modules[0], name="Interface A3", status=intf_status, role=intf_role),
|
|
2588
|
+
)
|
|
2589
|
+
|
|
2590
|
+
for module, ipaddress in zip(modules, ipaddresses):
|
|
2591
|
+
RelationshipAssociation(
|
|
2592
|
+
relationship=cls.relationships[0], source=module, destination=ipaddress
|
|
2593
|
+
).validated_save()
|
|
2594
|
+
|
|
2595
|
+
cls.form_data = {
|
|
2596
|
+
"module_type": moduletypes[1].pk,
|
|
2597
|
+
"role": moduleroles[1].pk,
|
|
2598
|
+
"tenant": None,
|
|
2599
|
+
"serial": "VMWARE-XX XX XX XX XX XX XX XX-XX XX XX XX XX XX XX XX",
|
|
2600
|
+
"asset_tag": generate_random_device_asset_tag_of_specified_size(100),
|
|
2601
|
+
"location": locations[1].pk,
|
|
2602
|
+
"status": statuses[1].pk,
|
|
2603
|
+
"tags": [t.pk for t in Tag.objects.get_for_model(Module)],
|
|
2604
|
+
"cf_crash_counter": -1,
|
|
2605
|
+
"cr_router-id": None,
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
cls.bulk_edit_data = {
|
|
2609
|
+
"role": moduleroles[1].pk,
|
|
2610
|
+
"tenant": None,
|
|
2611
|
+
"status": statuses[2].pk,
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2615
|
+
def test_module_consoleports(self):
|
|
2616
|
+
module = Module.objects.first()
|
|
1656
2617
|
|
|
1657
|
-
ConsolePort.objects.create(
|
|
1658
|
-
ConsolePort.objects.create(
|
|
1659
|
-
ConsolePort.objects.create(
|
|
2618
|
+
ConsolePort.objects.create(module=module, name="Console Port 1")
|
|
2619
|
+
ConsolePort.objects.create(module=module, name="Console Port 2")
|
|
2620
|
+
ConsolePort.objects.create(module=module, name="Console Port 3")
|
|
1660
2621
|
|
|
1661
|
-
url = reverse("dcim:
|
|
2622
|
+
url = reverse("dcim:module_consoleports", kwargs={"pk": module.pk})
|
|
1662
2623
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1663
2624
|
|
|
1664
2625
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1665
|
-
def
|
|
1666
|
-
|
|
2626
|
+
def test_module_consoleserverports(self):
|
|
2627
|
+
module = Module.objects.first()
|
|
1667
2628
|
|
|
1668
|
-
ConsoleServerPort.objects.create(
|
|
1669
|
-
ConsoleServerPort.objects.create(
|
|
1670
|
-
ConsoleServerPort.objects.create(
|
|
2629
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 1")
|
|
2630
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 2")
|
|
2631
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 3")
|
|
1671
2632
|
|
|
1672
|
-
url = reverse("dcim:
|
|
2633
|
+
url = reverse("dcim:module_consoleserverports", kwargs={"pk": module.pk})
|
|
1673
2634
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1674
2635
|
|
|
1675
2636
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1676
|
-
def
|
|
1677
|
-
|
|
2637
|
+
def test_module_powerports(self):
|
|
2638
|
+
module = Module.objects.first()
|
|
1678
2639
|
|
|
1679
|
-
PowerPort.objects.create(
|
|
1680
|
-
PowerPort.objects.create(
|
|
1681
|
-
PowerPort.objects.create(
|
|
2640
|
+
PowerPort.objects.create(module=module, name="Power Port 1")
|
|
2641
|
+
PowerPort.objects.create(module=module, name="Power Port 2")
|
|
2642
|
+
PowerPort.objects.create(module=module, name="Power Port 3")
|
|
1682
2643
|
|
|
1683
|
-
url = reverse("dcim:
|
|
2644
|
+
url = reverse("dcim:module_powerports", kwargs={"pk": module.pk})
|
|
1684
2645
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1685
2646
|
|
|
1686
2647
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1687
|
-
def
|
|
1688
|
-
|
|
2648
|
+
def test_module_poweroutlets(self):
|
|
2649
|
+
module = Module.objects.first()
|
|
1689
2650
|
|
|
1690
|
-
PowerOutlet.objects.create(
|
|
1691
|
-
PowerOutlet.objects.create(
|
|
1692
|
-
PowerOutlet.objects.create(
|
|
2651
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 1")
|
|
2652
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 2")
|
|
2653
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 3")
|
|
1693
2654
|
|
|
1694
|
-
url = reverse("dcim:
|
|
2655
|
+
url = reverse("dcim:module_poweroutlets", kwargs={"pk": module.pk})
|
|
1695
2656
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1696
2657
|
|
|
1697
2658
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1698
|
-
def
|
|
1699
|
-
|
|
2659
|
+
def test_module_interfaces(self):
|
|
2660
|
+
module = Module.objects.filter(interfaces__isnull=False).first()
|
|
1700
2661
|
self.add_permissions("ipam.add_ipaddress", "dcim.change_interface")
|
|
1701
2662
|
|
|
1702
|
-
url = reverse("dcim:
|
|
2663
|
+
url = reverse("dcim:module_interfaces", kwargs={"pk": module.pk})
|
|
1703
2664
|
response = self.client.get(url)
|
|
1704
2665
|
self.assertHttpStatus(response, 200)
|
|
1705
2666
|
response_body = response.content.decode(response.charset)
|
|
@@ -1708,16 +2669,16 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1708
2669
|
# Assert that "Add IP address" appears for each of the three interfaces
|
|
1709
2670
|
self.assertEqual(count, 3)
|
|
1710
2671
|
|
|
1711
|
-
def
|
|
1712
|
-
|
|
2672
|
+
def test_module_interface_assign_ipaddress(self):
|
|
2673
|
+
module = Module.objects.first()
|
|
1713
2674
|
self.add_permissions(
|
|
1714
2675
|
"ipam.add_ipaddress",
|
|
1715
2676
|
"extras.view_status",
|
|
1716
2677
|
"ipam.view_namespace",
|
|
1717
|
-
"dcim.
|
|
2678
|
+
"dcim.view_module",
|
|
1718
2679
|
"dcim.view_interface",
|
|
1719
2680
|
)
|
|
1720
|
-
|
|
2681
|
+
module_list_url = reverse("dcim:module_interfaces", args=(module.pk,))
|
|
1721
2682
|
namespace = Namespace.objects.first()
|
|
1722
2683
|
ipaddresses = [str(ipadress) for ipadress in IPAddress.objects.values_list("pk", flat=True)[:3]]
|
|
1723
2684
|
add_new_ip_form_data = {
|
|
@@ -1734,13 +2695,13 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1734
2695
|
"interface": self.interfaces[0].id,
|
|
1735
2696
|
}
|
|
1736
2697
|
add_new_ip_request = {
|
|
1737
|
-
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={
|
|
2698
|
+
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={module_list_url}",
|
|
1738
2699
|
"data": post_data(add_new_ip_form_data),
|
|
1739
2700
|
}
|
|
1740
2701
|
assign_ip_form_data = {"pk": ipaddresses}
|
|
1741
2702
|
assign_ip_request = {
|
|
1742
2703
|
"path": reverse("ipam:ipaddress_assign")
|
|
1743
|
-
+ f"?interface={self.interfaces[1].id}&return_url={
|
|
2704
|
+
+ f"?interface={self.interfaces[1].id}&return_url={module_list_url}",
|
|
1744
2705
|
"data": post_data(assign_ip_form_data),
|
|
1745
2706
|
}
|
|
1746
2707
|
|
|
@@ -1789,160 +2750,59 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1789
2750
|
sorted(interface_ips),
|
|
1790
2751
|
)
|
|
1791
2752
|
|
|
1792
|
-
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
1793
|
-
assign_ip_form_data["pk"] = []
|
|
1794
|
-
assign_ip_request = {
|
|
1795
|
-
"path": reverse("ipam:ipaddress_assign")
|
|
1796
|
-
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
1797
|
-
"data": post_data(assign_ip_form_data),
|
|
1798
|
-
}
|
|
1799
|
-
response = self.client.post(**assign_ip_request, follow=True)
|
|
1800
|
-
self.assertHttpStatus(response, 200)
|
|
1801
|
-
self.assertIn(
|
|
1802
|
-
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
1803
|
-
)
|
|
1804
|
-
|
|
1805
2753
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1806
|
-
def
|
|
1807
|
-
|
|
2754
|
+
def test_module_rearports(self):
|
|
2755
|
+
module = Module.objects.first()
|
|
1808
2756
|
|
|
1809
|
-
RearPort.objects.create(
|
|
1810
|
-
RearPort.objects.create(
|
|
1811
|
-
RearPort.objects.create(
|
|
2757
|
+
RearPort.objects.create(module=module, name="Rear Port 1")
|
|
2758
|
+
RearPort.objects.create(module=module, name="Rear Port 2")
|
|
2759
|
+
RearPort.objects.create(module=module, name="Rear Port 3")
|
|
1812
2760
|
|
|
1813
|
-
url = reverse("dcim:
|
|
2761
|
+
url = reverse("dcim:module_rearports", kwargs={"pk": module.pk})
|
|
1814
2762
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1815
2763
|
|
|
1816
2764
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1817
|
-
def
|
|
1818
|
-
|
|
2765
|
+
def test_module_frontports(self):
|
|
2766
|
+
module = Module.objects.first()
|
|
1819
2767
|
rear_ports = (
|
|
1820
|
-
RearPort.objects.create(
|
|
1821
|
-
RearPort.objects.create(
|
|
1822
|
-
RearPort.objects.create(
|
|
2768
|
+
RearPort.objects.create(module=module, name="Rear Port 1"),
|
|
2769
|
+
RearPort.objects.create(module=module, name="Rear Port 2"),
|
|
2770
|
+
RearPort.objects.create(module=module, name="Rear Port 3"),
|
|
1823
2771
|
)
|
|
1824
2772
|
|
|
1825
2773
|
FrontPort.objects.create(
|
|
1826
|
-
|
|
2774
|
+
module=module,
|
|
1827
2775
|
name="Front Port 1",
|
|
1828
2776
|
rear_port=rear_ports[0],
|
|
1829
2777
|
rear_port_position=1,
|
|
1830
2778
|
)
|
|
1831
2779
|
FrontPort.objects.create(
|
|
1832
|
-
|
|
2780
|
+
module=module,
|
|
1833
2781
|
name="Front Port 2",
|
|
1834
2782
|
rear_port=rear_ports[1],
|
|
1835
2783
|
rear_port_position=1,
|
|
1836
2784
|
)
|
|
1837
2785
|
FrontPort.objects.create(
|
|
1838
|
-
|
|
2786
|
+
module=module,
|
|
1839
2787
|
name="Front Port 3",
|
|
1840
2788
|
rear_port=rear_ports[2],
|
|
1841
2789
|
rear_port_position=1,
|
|
1842
2790
|
)
|
|
1843
2791
|
|
|
1844
|
-
url = reverse("dcim:
|
|
1845
|
-
self.assertHttpStatus(self.client.get(url), 200)
|
|
1846
|
-
|
|
1847
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1848
|
-
def test_device_devicebays(self):
|
|
1849
|
-
device = Device.objects.first()
|
|
1850
|
-
|
|
1851
|
-
# Device Bay 1 was already created in setUpTestData()
|
|
1852
|
-
DeviceBay.objects.create(device=device, name="Device Bay 2")
|
|
1853
|
-
DeviceBay.objects.create(device=device, name="Device Bay 3")
|
|
1854
|
-
|
|
1855
|
-
url = reverse("dcim:device_devicebays", kwargs={"pk": device.pk})
|
|
2792
|
+
url = reverse("dcim:module_frontports", kwargs={"pk": module.pk})
|
|
1856
2793
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1857
2794
|
|
|
1858
2795
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1859
|
-
def
|
|
1860
|
-
|
|
2796
|
+
def test_module_modulebays(self):
|
|
2797
|
+
module = Module.objects.first()
|
|
1861
2798
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2799
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 1")
|
|
2800
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 2")
|
|
2801
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 3")
|
|
1865
2802
|
|
|
1866
|
-
url = reverse("dcim:
|
|
2803
|
+
url = reverse("dcim:module_modulebays", kwargs={"pk": module.pk})
|
|
1867
2804
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1868
2805
|
|
|
1869
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1870
|
-
def test_device_primary_ips(self):
|
|
1871
|
-
"""Test assigning a primary IP to a device."""
|
|
1872
|
-
self.add_permissions("dcim.change_device")
|
|
1873
|
-
|
|
1874
|
-
# Create an interface and assign an IP to it.
|
|
1875
|
-
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
1876
|
-
interface = device.interfaces.first()
|
|
1877
|
-
namespace = Namespace.objects.first()
|
|
1878
|
-
Prefix.objects.create(prefix="1.2.3.0/24", namespace=namespace, status=self.prefix_status)
|
|
1879
|
-
ip_address = IPAddress.objects.create(address="1.2.3.4/32", namespace=namespace, status=self.ipaddr_status)
|
|
1880
|
-
interface.ip_addresses.add(ip_address)
|
|
1881
|
-
|
|
1882
|
-
# Dupe the form data and populated primary_ip4 w/ ip_address
|
|
1883
|
-
form_data = self.form_data.copy()
|
|
1884
|
-
form_data["primary_ip4"] = ip_address.pk
|
|
1885
|
-
# Assert that update succeeds.
|
|
1886
|
-
request = {
|
|
1887
|
-
"path": self._get_url("edit", device),
|
|
1888
|
-
"data": post_data(form_data),
|
|
1889
|
-
}
|
|
1890
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1891
|
-
self.assertInstanceEqual(self._get_queryset().order_by("last_updated").last(), form_data)
|
|
1892
|
-
|
|
1893
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1894
|
-
def test_local_config_context_schema_validation_pass(self):
|
|
1895
|
-
"""
|
|
1896
|
-
Given a config context schema
|
|
1897
|
-
And a device with local context that conforms to that schema
|
|
1898
|
-
Assert that the local context passes schema validation via full_clean()
|
|
1899
|
-
"""
|
|
1900
|
-
schema = ConfigContextSchema.objects.create(
|
|
1901
|
-
name="Schema 1",
|
|
1902
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
1903
|
-
)
|
|
1904
|
-
self.add_permissions("dcim.add_device")
|
|
1905
|
-
|
|
1906
|
-
form_data = self.form_data.copy()
|
|
1907
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1908
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1909
|
-
|
|
1910
|
-
# Try POST with model-level permission
|
|
1911
|
-
request = {
|
|
1912
|
-
"path": self._get_url("add"),
|
|
1913
|
-
"data": post_data(form_data),
|
|
1914
|
-
}
|
|
1915
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1916
|
-
self.assertEqual(
|
|
1917
|
-
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
1918
|
-
schema.pk,
|
|
1919
|
-
)
|
|
1920
|
-
|
|
1921
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1922
|
-
def test_local_config_context_schema_validation_fails(self):
|
|
1923
|
-
"""
|
|
1924
|
-
Given a config context schema
|
|
1925
|
-
And a device with local context that *does not* conform to that schema
|
|
1926
|
-
Assert that the local context fails schema validation via full_clean()
|
|
1927
|
-
"""
|
|
1928
|
-
schema = ConfigContextSchema.objects.create(
|
|
1929
|
-
name="Schema 1",
|
|
1930
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
1931
|
-
)
|
|
1932
|
-
self.add_permissions("dcim.add_device")
|
|
1933
|
-
|
|
1934
|
-
form_data = self.form_data.copy()
|
|
1935
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1936
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1937
|
-
|
|
1938
|
-
# Try POST with model-level permission
|
|
1939
|
-
request = {
|
|
1940
|
-
"path": self._get_url("add"),
|
|
1941
|
-
"data": post_data(form_data),
|
|
1942
|
-
}
|
|
1943
|
-
self.assertHttpStatus(self.client.post(**request), 200)
|
|
1944
|
-
self.assertEqual(self._get_queryset().filter(name="Device X").count(), 0)
|
|
1945
|
-
|
|
1946
2806
|
|
|
1947
2807
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1948
2808
|
model = ConsolePort
|
|
@@ -1983,6 +2843,15 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
1983
2843
|
"description": "New description",
|
|
1984
2844
|
}
|
|
1985
2845
|
|
|
2846
|
+
test_instance = cls.model.objects.first()
|
|
2847
|
+
cls.update_data = {
|
|
2848
|
+
"name": test_instance.name,
|
|
2849
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2850
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2851
|
+
"label": "new test label",
|
|
2852
|
+
"description": "new test description",
|
|
2853
|
+
}
|
|
2854
|
+
|
|
1986
2855
|
|
|
1987
2856
|
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1988
2857
|
model = ConsoleServerPort
|
|
@@ -2022,6 +2891,15 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2022
2891
|
"description": "New description",
|
|
2023
2892
|
}
|
|
2024
2893
|
|
|
2894
|
+
test_instance = cls.model.objects.first()
|
|
2895
|
+
cls.update_data = {
|
|
2896
|
+
"name": test_instance.name,
|
|
2897
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2898
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2899
|
+
"label": "new test label",
|
|
2900
|
+
"description": "new test description",
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2025
2903
|
|
|
2026
2904
|
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2027
2905
|
model = PowerPort
|
|
@@ -2066,12 +2944,22 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2066
2944
|
"description": "New description",
|
|
2067
2945
|
}
|
|
2068
2946
|
|
|
2947
|
+
test_instance = cls.model.objects.first()
|
|
2948
|
+
cls.update_data = {
|
|
2949
|
+
"name": test_instance.name,
|
|
2950
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2951
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2952
|
+
"label": "new test label",
|
|
2953
|
+
"description": "new test description",
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2069
2956
|
|
|
2070
2957
|
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2071
2958
|
model = PowerOutlet
|
|
2072
2959
|
|
|
2073
2960
|
@classmethod
|
|
2074
2961
|
def setUpTestData(cls):
|
|
2962
|
+
PowerOutlet.objects.all().delete()
|
|
2075
2963
|
device = create_test_device("Device 1")
|
|
2076
2964
|
|
|
2077
2965
|
powerports = (
|
|
@@ -2124,12 +3012,23 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2124
3012
|
"description": "New description",
|
|
2125
3013
|
}
|
|
2126
3014
|
|
|
3015
|
+
test_instance = cls.model.objects.first()
|
|
3016
|
+
cls.update_data = {
|
|
3017
|
+
"name": test_instance.name,
|
|
3018
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
3019
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
3020
|
+
"power_port": getattr(test_instance.power_port, "pk", None), # power_port must match parent device/module
|
|
3021
|
+
"label": "new test label",
|
|
3022
|
+
"description": "new test description",
|
|
3023
|
+
}
|
|
3024
|
+
|
|
2127
3025
|
|
|
2128
3026
|
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2129
3027
|
model = Interface
|
|
2130
3028
|
|
|
2131
3029
|
@classmethod
|
|
2132
3030
|
def setUpTestData(cls):
|
|
3031
|
+
Interface.objects.all().delete()
|
|
2133
3032
|
device = create_test_device("Device 1")
|
|
2134
3033
|
vrfs = list(VRF.objects.all()[:3])
|
|
2135
3034
|
for vrf in vrfs:
|
|
@@ -2137,22 +3036,24 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2137
3036
|
|
|
2138
3037
|
statuses = Status.objects.get_for_model(Interface)
|
|
2139
3038
|
status_active = statuses[0]
|
|
2140
|
-
|
|
3039
|
+
role = Role.objects.get_for_model(Interface).first()
|
|
2141
3040
|
interfaces = (
|
|
2142
|
-
Interface.objects.create(device=device, name="Interface
|
|
2143
|
-
Interface.objects.create(device=device, name="Interface
|
|
2144
|
-
Interface.objects.create(device=device, name="Interface
|
|
3041
|
+
Interface.objects.create(device=device, name="Interface A1", status=status_active, role=role),
|
|
3042
|
+
Interface.objects.create(device=device, name="Interface A2", status=status_active),
|
|
3043
|
+
Interface.objects.create(device=device, name="Interface A3", status=status_active, role=role),
|
|
2145
3044
|
Interface.objects.create(
|
|
2146
3045
|
device=device,
|
|
2147
3046
|
name="LAG",
|
|
2148
3047
|
status=status_active,
|
|
2149
3048
|
type=InterfaceTypeChoices.TYPE_LAG,
|
|
3049
|
+
role=role,
|
|
2150
3050
|
),
|
|
2151
3051
|
Interface.objects.create(
|
|
2152
3052
|
device=device,
|
|
2153
3053
|
name="BRIDGE",
|
|
2154
3054
|
status=status_active,
|
|
2155
3055
|
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
3056
|
+
role=role,
|
|
2156
3057
|
),
|
|
2157
3058
|
)
|
|
2158
3059
|
cls.lag_interface = interfaces[3]
|
|
@@ -2199,6 +3100,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2199
3100
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2200
3101
|
"enabled": False,
|
|
2201
3102
|
"status": status_active.pk,
|
|
3103
|
+
"role": role.pk,
|
|
2202
3104
|
"lag": interfaces[3].pk,
|
|
2203
3105
|
"mac_address": EUI("01:02:03:04:05:06"),
|
|
2204
3106
|
"mtu": 2000,
|
|
@@ -2227,6 +3129,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2227
3129
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2228
3130
|
"tags": [t.pk for t in Tag.objects.get_for_model(Interface)],
|
|
2229
3131
|
"status": status_active.pk,
|
|
3132
|
+
"role": role.pk,
|
|
2230
3133
|
"vrf": vrfs[0].pk,
|
|
2231
3134
|
}
|
|
2232
3135
|
|
|
@@ -2235,6 +3138,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2235
3138
|
"name_pattern": "Interface [4-6]",
|
|
2236
3139
|
"label_pattern": "Interface Number [4-6]",
|
|
2237
3140
|
"status": status_active.pk,
|
|
3141
|
+
"role": role.pk,
|
|
2238
3142
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2239
3143
|
"enabled": True,
|
|
2240
3144
|
"mtu": 1500,
|
|
@@ -2257,9 +3161,21 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2257
3161
|
"untagged_vlan": vlans[0].pk,
|
|
2258
3162
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2259
3163
|
"status": status_active.pk,
|
|
3164
|
+
"role": role.pk,
|
|
2260
3165
|
"vrf": vrfs[2].pk,
|
|
2261
3166
|
}
|
|
2262
3167
|
|
|
3168
|
+
test_instance = cls.model.objects.first()
|
|
3169
|
+
cls.update_data = {
|
|
3170
|
+
"name": test_instance.name,
|
|
3171
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3172
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3173
|
+
"status": test_instance.status.pk,
|
|
3174
|
+
"type": test_instance.type,
|
|
3175
|
+
"label": "new test label",
|
|
3176
|
+
"description": "new test description",
|
|
3177
|
+
}
|
|
3178
|
+
|
|
2263
3179
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2264
3180
|
def test_create_virtual_interface_with_parent_lag(self):
|
|
2265
3181
|
"""https://github.com/nautobot/nautobot/issues/4436."""
|
|
@@ -2305,18 +3221,66 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2305
3221
|
cls.device = device
|
|
2306
3222
|
|
|
2307
3223
|
rearports = (
|
|
2308
|
-
RearPort.objects.create(
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3224
|
+
RearPort.objects.create(
|
|
3225
|
+
device=device,
|
|
3226
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3227
|
+
positions=24,
|
|
3228
|
+
name="Rear Port 1",
|
|
3229
|
+
),
|
|
3230
|
+
RearPort.objects.create(
|
|
3231
|
+
device=device,
|
|
3232
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3233
|
+
positions=24,
|
|
3234
|
+
name="Rear Port 2",
|
|
3235
|
+
),
|
|
3236
|
+
RearPort.objects.create(
|
|
3237
|
+
device=device,
|
|
3238
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3239
|
+
positions=24,
|
|
3240
|
+
name="Rear Port 3",
|
|
3241
|
+
),
|
|
3242
|
+
RearPort.objects.create(
|
|
3243
|
+
device=device,
|
|
3244
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3245
|
+
positions=24,
|
|
3246
|
+
name="Rear Port 4",
|
|
3247
|
+
),
|
|
3248
|
+
RearPort.objects.create(
|
|
3249
|
+
device=device,
|
|
3250
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3251
|
+
positions=24,
|
|
3252
|
+
name="Rear Port 5",
|
|
3253
|
+
),
|
|
3254
|
+
RearPort.objects.create(
|
|
3255
|
+
device=device,
|
|
3256
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3257
|
+
positions=24,
|
|
3258
|
+
name="Rear Port 6",
|
|
3259
|
+
),
|
|
2314
3260
|
)
|
|
2315
3261
|
|
|
2316
3262
|
frontports = (
|
|
2317
|
-
FrontPort.objects.create(
|
|
2318
|
-
|
|
2319
|
-
|
|
3263
|
+
FrontPort.objects.create(
|
|
3264
|
+
device=device,
|
|
3265
|
+
name="Front Port 1",
|
|
3266
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3267
|
+
rear_port=rearports[0],
|
|
3268
|
+
rear_port_position=12,
|
|
3269
|
+
),
|
|
3270
|
+
FrontPort.objects.create(
|
|
3271
|
+
device=device,
|
|
3272
|
+
name="Front Port 2",
|
|
3273
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3274
|
+
rear_port=rearports[1],
|
|
3275
|
+
rear_port_position=12,
|
|
3276
|
+
),
|
|
3277
|
+
FrontPort.objects.create(
|
|
3278
|
+
device=device,
|
|
3279
|
+
name="Front Port 3",
|
|
3280
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3281
|
+
rear_port=rearports[2],
|
|
3282
|
+
rear_port_position=12,
|
|
3283
|
+
),
|
|
2320
3284
|
)
|
|
2321
3285
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2322
3286
|
cls.selected_objects = frontports
|
|
@@ -2346,6 +3310,18 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2346
3310
|
"description": "New description",
|
|
2347
3311
|
}
|
|
2348
3312
|
|
|
3313
|
+
test_instance = cls.model.objects.first()
|
|
3314
|
+
cls.update_data = {
|
|
3315
|
+
"name": test_instance.name,
|
|
3316
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3317
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3318
|
+
"rear_port": test_instance.rear_port.pk, # rear_port must match the parent device/module
|
|
3319
|
+
"rear_port_position": test_instance.rear_port_position,
|
|
3320
|
+
"type": test_instance.type,
|
|
3321
|
+
"label": "new test label",
|
|
3322
|
+
"description": "new test description",
|
|
3323
|
+
}
|
|
3324
|
+
|
|
2349
3325
|
@unittest.skip("No DeviceBulkAddFrontPortView exists at present")
|
|
2350
3326
|
def test_bulk_add_component(self):
|
|
2351
3327
|
pass
|
|
@@ -2359,9 +3335,24 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2359
3335
|
device = create_test_device("Device 1")
|
|
2360
3336
|
|
|
2361
3337
|
rearports = (
|
|
2362
|
-
RearPort.objects.create(
|
|
2363
|
-
|
|
2364
|
-
|
|
3338
|
+
RearPort.objects.create(
|
|
3339
|
+
device=device,
|
|
3340
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3341
|
+
positions=24,
|
|
3342
|
+
name="Rear Port 1",
|
|
3343
|
+
),
|
|
3344
|
+
RearPort.objects.create(
|
|
3345
|
+
device=device,
|
|
3346
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3347
|
+
positions=24,
|
|
3348
|
+
name="Rear Port 2",
|
|
3349
|
+
),
|
|
3350
|
+
RearPort.objects.create(
|
|
3351
|
+
device=device,
|
|
3352
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3353
|
+
positions=24,
|
|
3354
|
+
name="Rear Port 3",
|
|
3355
|
+
),
|
|
2365
3356
|
)
|
|
2366
3357
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2367
3358
|
cls.selected_objects = rearports
|
|
@@ -2390,6 +3381,17 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2390
3381
|
"description": "New description",
|
|
2391
3382
|
}
|
|
2392
3383
|
|
|
3384
|
+
test_instance = cls.model.objects.first()
|
|
3385
|
+
cls.update_data = {
|
|
3386
|
+
"name": test_instance.name,
|
|
3387
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3388
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3389
|
+
"positions": test_instance.positions,
|
|
3390
|
+
"type": test_instance.type,
|
|
3391
|
+
"label": "new test label",
|
|
3392
|
+
"description": "new test description",
|
|
3393
|
+
}
|
|
3394
|
+
|
|
2393
3395
|
|
|
2394
3396
|
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2395
3397
|
model = DeviceBay
|
|
@@ -2428,6 +3430,113 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2428
3430
|
"description": "New description",
|
|
2429
3431
|
}
|
|
2430
3432
|
|
|
3433
|
+
test_instance = cls.model.objects.first()
|
|
3434
|
+
cls.update_data = {
|
|
3435
|
+
"name": test_instance.name,
|
|
3436
|
+
"device": test_instance.device.pk,
|
|
3437
|
+
"label": "new test label",
|
|
3438
|
+
"description": "new test description",
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
|
|
3442
|
+
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
3443
|
+
model = ModuleBay
|
|
3444
|
+
|
|
3445
|
+
@classmethod
|
|
3446
|
+
def setUpTestData(cls):
|
|
3447
|
+
device = Device.objects.first()
|
|
3448
|
+
module = Module.objects.first()
|
|
3449
|
+
|
|
3450
|
+
module_bays = (
|
|
3451
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 1"),
|
|
3452
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 2"),
|
|
3453
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 3"),
|
|
3454
|
+
)
|
|
3455
|
+
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
3456
|
+
cls.selected_objects = module_bays
|
|
3457
|
+
cls.selected_objects_parent_name = device.name
|
|
3458
|
+
|
|
3459
|
+
cls.form_data = {
|
|
3460
|
+
"parent_device": device.pk,
|
|
3461
|
+
"name": "Test ModuleBay 1",
|
|
3462
|
+
"position": 1,
|
|
3463
|
+
"description": "Test modulebay description",
|
|
3464
|
+
"label": "Test modulebay label",
|
|
3465
|
+
"tags": sorted([t.pk for t in Tag.objects.get_for_model(ModuleBay)]),
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
cls.bulk_create_data = {
|
|
3469
|
+
"parent_module": module.pk,
|
|
3470
|
+
"name_pattern": "Test ModuleBay [0-2]",
|
|
3471
|
+
"position_pattern": "[1-3]",
|
|
3472
|
+
# Test that a label can be applied to each generated module bay
|
|
3473
|
+
"label_pattern": "Slot[1-3]",
|
|
3474
|
+
"description": "Test modulebay description",
|
|
3475
|
+
"tags": sorted([t.pk for t in Tag.objects.get_for_model(ModuleBay)]),
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
cls.bulk_edit_data = {
|
|
3479
|
+
"position": "new position",
|
|
3480
|
+
"description": "New description",
|
|
3481
|
+
"label": "New label",
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
test_instance = cls.model.objects.first()
|
|
3485
|
+
cls.update_data = {
|
|
3486
|
+
"name": test_instance.name,
|
|
3487
|
+
"parent_device": getattr(getattr(test_instance, "parent_device", None), "pk", None),
|
|
3488
|
+
"parent_module": getattr(getattr(test_instance, "parent_module", None), "pk", None),
|
|
3489
|
+
"position": "new test position",
|
|
3490
|
+
"label": "new test label",
|
|
3491
|
+
"description": "new test description",
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
def get_deletable_object_pks(self):
|
|
3495
|
+
# Since Modules and ModuleBays are nestable, we need to delete ModuleBays that don't have any child ModuleBays
|
|
3496
|
+
return ModuleBay.objects.filter(installed_module__isnull=True).values_list("pk", flat=True)[:3]
|
|
3497
|
+
|
|
3498
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3499
|
+
def test_bulk_add_component(self):
|
|
3500
|
+
"""Test bulk-adding this component to modules."""
|
|
3501
|
+
obj_perm = ObjectPermission(name="Test permission", actions=["add"])
|
|
3502
|
+
obj_perm.save()
|
|
3503
|
+
obj_perm.users.add(self.user)
|
|
3504
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
3505
|
+
|
|
3506
|
+
initial_count = self._get_queryset().count()
|
|
3507
|
+
|
|
3508
|
+
data = self.bulk_create_data.copy()
|
|
3509
|
+
|
|
3510
|
+
# Load the module-bulk-add form
|
|
3511
|
+
module_perm = ObjectPermission(name="Module permission", actions=["change"])
|
|
3512
|
+
module_perm.save()
|
|
3513
|
+
module_perm.users.add(self.user)
|
|
3514
|
+
module_perm.object_types.add(ContentType.objects.get_for_model(Module))
|
|
3515
|
+
url = reverse(f"dcim:module_bulk_add_{self.model._meta.model_name}")
|
|
3516
|
+
request = {
|
|
3517
|
+
"path": url,
|
|
3518
|
+
"data": post_data({"pk": data["parent_module"]}),
|
|
3519
|
+
}
|
|
3520
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
3521
|
+
|
|
3522
|
+
# Post to the module-bulk-add form to create records
|
|
3523
|
+
data["pk"] = data.pop("parent_module")
|
|
3524
|
+
data["_create"] = ""
|
|
3525
|
+
request["data"] = post_data(data)
|
|
3526
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
3527
|
+
|
|
3528
|
+
updated_count = self._get_queryset().count()
|
|
3529
|
+
self.assertEqual(updated_count, initial_count + self.bulk_create_count)
|
|
3530
|
+
|
|
3531
|
+
matching_count = 0
|
|
3532
|
+
for instance in self._get_queryset().all():
|
|
3533
|
+
try:
|
|
3534
|
+
self.assertInstanceEqual(instance, self.bulk_create_data)
|
|
3535
|
+
matching_count += 1
|
|
3536
|
+
except AssertionError:
|
|
3537
|
+
pass
|
|
3538
|
+
self.assertEqual(matching_count, self.bulk_create_count)
|
|
3539
|
+
|
|
2431
3540
|
|
|
2432
3541
|
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2433
3542
|
model = InventoryItem
|
|
@@ -2480,6 +3589,14 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2480
3589
|
"software_version": software_versions[2].pk,
|
|
2481
3590
|
}
|
|
2482
3591
|
|
|
3592
|
+
test_instance = cls.model.objects.first()
|
|
3593
|
+
cls.update_data = {
|
|
3594
|
+
"name": test_instance.name,
|
|
3595
|
+
"device": test_instance.device.pk,
|
|
3596
|
+
"label": "new test label",
|
|
3597
|
+
"description": "new test description",
|
|
3598
|
+
}
|
|
3599
|
+
|
|
2483
3600
|
def test_table_with_indentation_is_removed_on_filter_or_sort(self):
|
|
2484
3601
|
self.skipTest("InventoryItem table has no implementation of indentation.")
|
|
2485
3602
|
|
|
@@ -2540,73 +3657,73 @@ class CableTestCase(
|
|
|
2540
3657
|
interfaces = (
|
|
2541
3658
|
Interface.objects.create(
|
|
2542
3659
|
device=devices[0],
|
|
2543
|
-
name="Interface
|
|
3660
|
+
name="Interface A1",
|
|
2544
3661
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2545
3662
|
status=interface_status,
|
|
2546
3663
|
),
|
|
2547
3664
|
Interface.objects.create(
|
|
2548
3665
|
device=devices[0],
|
|
2549
|
-
name="Interface
|
|
3666
|
+
name="Interface A2",
|
|
2550
3667
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2551
3668
|
status=interface_status,
|
|
2552
3669
|
),
|
|
2553
3670
|
Interface.objects.create(
|
|
2554
3671
|
device=devices[0],
|
|
2555
|
-
name="Interface
|
|
3672
|
+
name="Interface A3",
|
|
2556
3673
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2557
3674
|
status=interface_status,
|
|
2558
3675
|
),
|
|
2559
3676
|
Interface.objects.create(
|
|
2560
3677
|
device=devices[1],
|
|
2561
|
-
name="Interface
|
|
3678
|
+
name="Interface A1",
|
|
2562
3679
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2563
3680
|
status=interface_status,
|
|
2564
3681
|
),
|
|
2565
3682
|
Interface.objects.create(
|
|
2566
3683
|
device=devices[1],
|
|
2567
|
-
name="Interface
|
|
3684
|
+
name="Interface A2",
|
|
2568
3685
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2569
3686
|
status=interface_status,
|
|
2570
3687
|
),
|
|
2571
3688
|
Interface.objects.create(
|
|
2572
3689
|
device=devices[1],
|
|
2573
|
-
name="Interface
|
|
3690
|
+
name="Interface A3",
|
|
2574
3691
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2575
3692
|
status=interface_status,
|
|
2576
3693
|
),
|
|
2577
3694
|
Interface.objects.create(
|
|
2578
3695
|
device=devices[2],
|
|
2579
|
-
name="Interface
|
|
3696
|
+
name="Interface A1",
|
|
2580
3697
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2581
3698
|
status=interface_status,
|
|
2582
3699
|
),
|
|
2583
3700
|
Interface.objects.create(
|
|
2584
3701
|
device=devices[2],
|
|
2585
|
-
name="Interface
|
|
3702
|
+
name="Interface A2",
|
|
2586
3703
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2587
3704
|
status=interface_status,
|
|
2588
3705
|
),
|
|
2589
3706
|
Interface.objects.create(
|
|
2590
3707
|
device=devices[2],
|
|
2591
|
-
name="Interface
|
|
3708
|
+
name="Interface A3",
|
|
2592
3709
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2593
3710
|
status=interface_status,
|
|
2594
3711
|
),
|
|
2595
3712
|
Interface.objects.create(
|
|
2596
3713
|
device=devices[3],
|
|
2597
|
-
name="Interface
|
|
3714
|
+
name="Interface A1",
|
|
2598
3715
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2599
3716
|
status=interface_status,
|
|
2600
3717
|
),
|
|
2601
3718
|
Interface.objects.create(
|
|
2602
3719
|
device=devices[3],
|
|
2603
|
-
name="Interface
|
|
3720
|
+
name="Interface A2",
|
|
2604
3721
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2605
3722
|
status=interface_status,
|
|
2606
3723
|
),
|
|
2607
3724
|
Interface.objects.create(
|
|
2608
3725
|
device=devices[3],
|
|
2609
|
-
name="Interface
|
|
3726
|
+
name="Interface A3",
|
|
2610
3727
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2611
3728
|
status=interface_status,
|
|
2612
3729
|
),
|
|
@@ -2758,6 +3875,9 @@ class ConsoleConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2758
3875
|
def _get_base_url(self):
|
|
2759
3876
|
return "dcim:console_connections_{}"
|
|
2760
3877
|
|
|
3878
|
+
def _get_queryset(self):
|
|
3879
|
+
return ConsolePort.objects.filter(cable__isnull=False)
|
|
3880
|
+
|
|
2761
3881
|
def get_list_url(self):
|
|
2762
3882
|
return "/dcim/console-connections/"
|
|
2763
3883
|
|
|
@@ -2810,15 +3930,18 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2810
3930
|
Test the PowerConnectionsListView.
|
|
2811
3931
|
"""
|
|
2812
3932
|
|
|
3933
|
+
def _get_base_url(self):
|
|
3934
|
+
return "dcim:power_connections_{}"
|
|
3935
|
+
|
|
3936
|
+
def _get_queryset(self):
|
|
3937
|
+
return PowerPort.objects.filter(cable__isnull=False)
|
|
3938
|
+
|
|
2813
3939
|
def get_list_url(self):
|
|
2814
3940
|
return "/dcim/power-connections/"
|
|
2815
3941
|
|
|
2816
3942
|
def get_title(self):
|
|
2817
3943
|
return "Power Connections"
|
|
2818
3944
|
|
|
2819
|
-
def _get_base_url(self):
|
|
2820
|
-
return "dcim:power_connections_{}"
|
|
2821
|
-
|
|
2822
3945
|
def get_list_view(self):
|
|
2823
3946
|
return PowerConnectionsListView
|
|
2824
3947
|
|
|
@@ -2875,6 +3998,9 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2875
3998
|
def _get_base_url(self):
|
|
2876
3999
|
return "dcim:interface_connections_{}"
|
|
2877
4000
|
|
|
4001
|
+
def _get_queryset(self):
|
|
4002
|
+
return Interface.objects.filter(cable__isnull=False)
|
|
4003
|
+
|
|
2878
4004
|
def get_list_url(self):
|
|
2879
4005
|
return "/dcim/interface-connections/"
|
|
2880
4006
|
|
|
@@ -2895,22 +4021,25 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2895
4021
|
device_2 = create_test_device("Device 2")
|
|
2896
4022
|
|
|
2897
4023
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
4024
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
2898
4025
|
cls.interfaces = (
|
|
2899
4026
|
Interface.objects.create(
|
|
2900
4027
|
device=device_1,
|
|
2901
|
-
name="Interface
|
|
4028
|
+
name="Interface A1",
|
|
2902
4029
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2903
4030
|
status=interface_status,
|
|
4031
|
+
role=interface_role,
|
|
2904
4032
|
),
|
|
2905
4033
|
Interface.objects.create(
|
|
2906
4034
|
device=device_1,
|
|
2907
|
-
name="Interface
|
|
4035
|
+
name="Interface A2",
|
|
2908
4036
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2909
4037
|
status=interface_status,
|
|
4038
|
+
role=interface_role,
|
|
2910
4039
|
),
|
|
2911
4040
|
Interface.objects.create(
|
|
2912
4041
|
device=device_1,
|
|
2913
|
-
name="Interface
|
|
4042
|
+
name="Interface A3",
|
|
2914
4043
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2915
4044
|
status=interface_status,
|
|
2916
4045
|
),
|
|
@@ -2918,9 +4047,10 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2918
4047
|
|
|
2919
4048
|
cls.device_2_interface = Interface.objects.create(
|
|
2920
4049
|
device=device_2,
|
|
2921
|
-
name="Interface
|
|
4050
|
+
name="Interface A1",
|
|
2922
4051
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2923
4052
|
status=interface_status,
|
|
4053
|
+
role=interface_role,
|
|
2924
4054
|
)
|
|
2925
4055
|
rearport = RearPort.objects.create(device=device_2, type=PortTypeChoices.TYPE_8P8C)
|
|
2926
4056
|
|
|
@@ -3326,11 +4456,11 @@ class InterfaceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3326
4456
|
status=status_active,
|
|
3327
4457
|
)
|
|
3328
4458
|
intf_status = Status.objects.get_for_model(Interface).first()
|
|
3329
|
-
|
|
4459
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
3330
4460
|
cls.interfaces = (
|
|
3331
|
-
Interface.objects.create(device=device, name="Interface
|
|
3332
|
-
Interface.objects.create(device=device, name="Interface
|
|
3333
|
-
Interface.objects.create(device=device, name="Interface
|
|
4461
|
+
Interface.objects.create(device=device, name="Interface A1", status=intf_status, role=intf_role),
|
|
4462
|
+
Interface.objects.create(device=device, name="Interface A2", status=intf_status),
|
|
4463
|
+
Interface.objects.create(device=device, name="Interface A3", status=intf_status, role=intf_role),
|
|
3334
4464
|
)
|
|
3335
4465
|
|
|
3336
4466
|
cls.form_data = {
|