nautobot 2.2.8__py3-none-any.whl → 2.3.0__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 +113 -0
- nautobot/cloud/filters.py +187 -0
- nautobot/cloud/forms.py +339 -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 +246 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +157 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -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 +125 -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 +110 -14
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +11 -3
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +28 -7
- nautobot/core/jobs/__init__.py +20 -3
- 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 +15 -19
- nautobot/core/settings.yaml +48 -13
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +130 -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 +28 -8
- 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 +63 -14
- 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 +204 -2
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_templatetags_netutils.py +3 -3
- 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 +119 -67
- nautobot/core/views/mixins.py +105 -36
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +81 -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 +252 -18
- nautobot/dcim/filters/__init__.py +373 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +834 -121
- 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 +862 -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 +282 -99
- nautobot/dcim/models/devices.py +472 -13
- nautobot/dcim/models/racks.py +0 -1
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +248 -47
- 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 +14 -6
- 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 +6 -0
- 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 +159 -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 +693 -208
- nautobot/dcim/tests/test_filters.py +843 -217
- nautobot/dcim/tests/test_models.py +1103 -8
- nautobot/dcim/tests/test_views.py +1525 -343
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1109 -113
- 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 +73 -59
- 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 +460 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +24 -5
- 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 +26 -3
- 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 +87 -3
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +118 -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 +109 -101
- nautobot/extras/tables.py +201 -17
- 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 +11 -0
- 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 +509 -23
- 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 +6 -6
- nautobot/extras/tests/test_models.py +501 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +586 -8
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +85 -16
- nautobot/extras/views.py +562 -122
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +13 -0
- nautobot/ipam/forms.py +8 -4
- 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 +20 -20
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +5 -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 +9 -2
- 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 +20 -34
- nautobot/project-static/css/base.css +21 -0
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +894 -90
- nautobot/project-static/docs/apps/index.html +894 -90
- nautobot/project-static/docs/apps/nautobot-apps.html +894 -90
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +921 -122
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +906 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1620 -905
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +937 -146
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +979 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +903 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +899 -95
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +993 -195
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +976 -133
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1080 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1244 -336
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1729 -877
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1166 -383
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2090 -1376
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2248 -1424
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +914 -113
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +965 -165
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1012 -225
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1915 -1279
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1848 -1104
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +906 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2335 -1701
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1804 -1026
- nautobot/project-static/docs/development/apps/api/configuration-view.html +894 -90
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/global-search.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/graphql.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/index.html +944 -92
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +894 -90
- nautobot/project-static/docs/development/apps/api/prometheus.html +894 -90
- nautobot/project-static/docs/development/apps/api/setup.html +894 -90
- nautobot/project-static/docs/development/apps/api/testing.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/base-template.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/notes.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/urls.html +894 -90
- nautobot/project-static/docs/development/apps/index.html +894 -90
- nautobot/project-static/docs/development/apps/migration/code-updates.html +894 -90
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +894 -90
- nautobot/project-static/docs/development/apps/migration/from-v1.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +894 -90
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +894 -90
- nautobot/project-static/docs/development/core/application-registry.html +894 -90
- nautobot/project-static/docs/development/core/best-practices.html +895 -90
- nautobot/project-static/docs/development/core/bootstrap-ui.html +894 -90
- nautobot/project-static/docs/development/core/caching.html +894 -90
- nautobot/project-static/docs/development/core/controllers.html +894 -90
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +894 -90
- nautobot/project-static/docs/development/core/generic-views.html +894 -90
- nautobot/project-static/docs/development/core/getting-started.html +894 -90
- nautobot/project-static/docs/development/core/homepage.html +894 -90
- nautobot/project-static/docs/development/core/index.html +905 -90
- nautobot/project-static/docs/development/core/model-checklist.html +903 -91
- nautobot/project-static/docs/development/core/model-features.html +894 -90
- nautobot/project-static/docs/development/core/natural-keys.html +894 -90
- nautobot/project-static/docs/development/core/navigation-menu.html +894 -90
- nautobot/project-static/docs/development/core/release-checklist.html +897 -93
- nautobot/project-static/docs/development/core/role-internals.html +894 -90
- nautobot/project-static/docs/development/core/settings.html +894 -90
- nautobot/project-static/docs/development/core/style-guide.html +895 -91
- nautobot/project-static/docs/development/core/templates.html +906 -91
- nautobot/project-static/docs/development/core/testing.html +894 -90
- nautobot/project-static/docs/development/core/user-preferences.html +894 -90
- nautobot/project-static/docs/development/index.html +894 -90
- nautobot/project-static/docs/development/jobs/index.html +1271 -453
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +894 -90
- nautobot/project-static/docs/index.html +9032 -13
- 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 +902 -91
- nautobot/project-static/docs/overview/design_philosophy.html +896 -92
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +1131 -94
- nautobot/project-static/docs/release-notes/version-1.0.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.1.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.2.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.3.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.4.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.5.html +895 -91
- nautobot/project-static/docs/release-notes/version-1.6.html +895 -91
- nautobot/project-static/docs/release-notes/version-2.0.html +894 -90
- nautobot/project-static/docs/release-notes/version-2.1.html +894 -90
- nautobot/project-static/docs/release-notes/version-2.2.html +1137 -196
- nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
- nautobot/project-static/docs/requirements.txt +5 -5
- 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 +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +1025 -175
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +902 -90
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +946 -155
- nautobot/project-static/docs/user-guide/administration/installation/index.html +903 -95
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +936 -124
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +956 -159
- nautobot/project-static/docs/user-guide/administration/installation/services.html +915 -114
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +910 -101
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +894 -90
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +894 -90
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +894 -90
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +977 -121
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +895 -91
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +898 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +897 -93
- 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 +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +915 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +922 -118
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +923 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +916 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +938 -120
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +930 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +939 -121
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +930 -112
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +923 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +925 -117
- 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 +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +942 -85
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +945 -88
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +931 -127
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +938 -90
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +899 -91
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +899 -91
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +903 -98
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +899 -91
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +894 -90
- 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 +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +894 -90
- nautobot/project-static/docs/user-guide/index.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1260 -787
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +898 -90
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +952 -123
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +894 -90
- 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 +24 -20
- 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/models.py +0 -2
- nautobot/virtualization/tables.py +12 -8
- 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.8.dist-info → nautobot-2.3.0.dist-info}/METADATA +21 -19
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/RECORD +684 -564
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
- 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.8.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.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,9 +815,9 @@ 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,
|
|
820
|
+
"comments": "changed comment",
|
|
809
821
|
}
|
|
810
822
|
|
|
811
823
|
def test_list_has_correct_links(self):
|
|
@@ -817,29 +829,32 @@ class DeviceTypeTestCase(
|
|
|
817
829
|
|
|
818
830
|
yaml_import_url = reverse("dcim:devicetype_import")
|
|
819
831
|
csv_import_url = job_import_url(ContentType.objects.get_for_model(DeviceType))
|
|
820
|
-
#
|
|
832
|
+
# Dropdown provides both YAML/JSON and CSV import as options
|
|
821
833
|
self.assertInHTML(
|
|
822
|
-
f'<a
|
|
823
|
-
|
|
834
|
+
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>',
|
|
835
|
+
content,
|
|
836
|
+
)
|
|
837
|
+
self.assertInHTML(
|
|
838
|
+
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
839
|
content,
|
|
825
840
|
)
|
|
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
841
|
|
|
830
842
|
export_url = job_export_url()
|
|
831
843
|
# Export is a little trickier to check since it's done as a form submission rather than an <a> element.
|
|
832
844
|
self.assertIn(f'<form action="{export_url}" method="post">', content)
|
|
833
845
|
self.assertInHTML(
|
|
834
|
-
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(
|
|
846
|
+
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(self.model).pk}">',
|
|
835
847
|
content,
|
|
836
848
|
)
|
|
837
849
|
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
838
850
|
self.assertInHTML(
|
|
839
|
-
'<button type="submit" class="
|
|
851
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as YAML</button>',
|
|
852
|
+
content,
|
|
853
|
+
)
|
|
854
|
+
self.assertInHTML(
|
|
855
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as CSV</button>',
|
|
840
856
|
content,
|
|
841
857
|
)
|
|
842
|
-
self.assertInHTML('<button type="submit" class="btn btn-link">CSV format</button>', content)
|
|
843
858
|
|
|
844
859
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
845
860
|
def test_import_objects(self):
|
|
@@ -920,6 +935,13 @@ device-bays:
|
|
|
920
935
|
- name: Device Bay 1
|
|
921
936
|
- name: Device Bay 2
|
|
922
937
|
- name: Device Bay 3
|
|
938
|
+
module-bays:
|
|
939
|
+
- name: Module Bay 1
|
|
940
|
+
position: 1
|
|
941
|
+
- name: Module Bay 2
|
|
942
|
+
position: 2
|
|
943
|
+
- name: Module Bay 3
|
|
944
|
+
position: 3
|
|
923
945
|
"""
|
|
924
946
|
|
|
925
947
|
# Add all required permissions to the test user
|
|
@@ -934,6 +956,7 @@ device-bays:
|
|
|
934
956
|
"dcim.add_frontporttemplate",
|
|
935
957
|
"dcim.add_rearporttemplate",
|
|
936
958
|
"dcim.add_devicebaytemplate",
|
|
959
|
+
"dcim.add_modulebaytemplate",
|
|
937
960
|
)
|
|
938
961
|
|
|
939
962
|
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
@@ -944,47 +967,51 @@ device-bays:
|
|
|
944
967
|
|
|
945
968
|
# Verify all of the components were created
|
|
946
969
|
self.assertEqual(dt.console_port_templates.count(), 3)
|
|
947
|
-
cp1 =
|
|
970
|
+
cp1 = dt.console_port_templates.first()
|
|
948
971
|
self.assertEqual(cp1.name, "Console Port 1")
|
|
949
972
|
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
|
950
973
|
|
|
951
974
|
self.assertEqual(dt.console_server_port_templates.count(), 3)
|
|
952
|
-
csp1 =
|
|
975
|
+
csp1 = dt.console_server_port_templates.first()
|
|
953
976
|
self.assertEqual(csp1.name, "Console Server Port 1")
|
|
954
977
|
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
|
955
978
|
|
|
956
979
|
self.assertEqual(dt.power_port_templates.count(), 3)
|
|
957
|
-
pp1 =
|
|
980
|
+
pp1 = dt.power_port_templates.first()
|
|
958
981
|
self.assertEqual(pp1.name, "Power Port 1")
|
|
959
982
|
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
|
960
983
|
|
|
961
984
|
self.assertEqual(dt.power_outlet_templates.count(), 3)
|
|
962
|
-
po1 =
|
|
985
|
+
po1 = dt.power_outlet_templates.first()
|
|
963
986
|
self.assertEqual(po1.name, "Power Outlet 1")
|
|
964
987
|
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
|
965
988
|
self.assertEqual(po1.power_port_template, pp1)
|
|
966
989
|
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
|
967
990
|
|
|
968
991
|
self.assertEqual(dt.interface_templates.count(), 3)
|
|
969
|
-
iface1 =
|
|
992
|
+
iface1 = dt.interface_templates.first()
|
|
970
993
|
self.assertEqual(iface1.name, "Interface 1")
|
|
971
994
|
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
|
972
995
|
self.assertTrue(iface1.mgmt_only)
|
|
973
996
|
|
|
974
997
|
self.assertEqual(dt.rear_port_templates.count(), 3)
|
|
975
|
-
rp1 =
|
|
998
|
+
rp1 = dt.rear_port_templates.first()
|
|
976
999
|
self.assertEqual(rp1.name, "Rear Port 1")
|
|
977
1000
|
|
|
978
1001
|
self.assertEqual(dt.front_port_templates.count(), 3)
|
|
979
|
-
fp1 =
|
|
1002
|
+
fp1 = dt.front_port_templates.first()
|
|
980
1003
|
self.assertEqual(fp1.name, "Front Port 1")
|
|
981
1004
|
self.assertEqual(fp1.rear_port_template, rp1)
|
|
982
1005
|
self.assertEqual(fp1.rear_port_position, 1)
|
|
983
1006
|
|
|
984
1007
|
self.assertEqual(dt.device_bay_templates.count(), 3)
|
|
985
|
-
db1 =
|
|
1008
|
+
db1 = dt.device_bay_templates.first()
|
|
986
1009
|
self.assertEqual(db1.name, "Device Bay 1")
|
|
987
1010
|
|
|
1011
|
+
self.assertEqual(dt.module_bay_templates.count(), 3)
|
|
1012
|
+
mb1 = dt.module_bay_templates.first()
|
|
1013
|
+
self.assertEqual(mb1.name, "Module Bay 1")
|
|
1014
|
+
|
|
988
1015
|
def test_import_objects_unknown_type_enums(self):
|
|
989
1016
|
"""
|
|
990
1017
|
YAML import of data with `type` values that we don't recognize should remap those to "other" rather than fail.
|
|
@@ -1023,6 +1050,13 @@ device-bays:
|
|
|
1023
1050
|
- name: Device Bay of Uncertain Type
|
|
1024
1051
|
type: unknown # should be ignored
|
|
1025
1052
|
- name: Device Bay of Unspecified Type
|
|
1053
|
+
module-bays:
|
|
1054
|
+
- name: Module Bay 1
|
|
1055
|
+
position: 1
|
|
1056
|
+
- name: Module Bay 2
|
|
1057
|
+
position: 2
|
|
1058
|
+
- name: Module Bay 3
|
|
1059
|
+
position: 3
|
|
1026
1060
|
"""
|
|
1027
1061
|
# Add all required permissions to the test user
|
|
1028
1062
|
self.add_permissions(
|
|
@@ -1037,6 +1071,7 @@ device-bays:
|
|
|
1037
1071
|
"dcim.add_frontporttemplate",
|
|
1038
1072
|
"dcim.add_rearporttemplate",
|
|
1039
1073
|
"dcim.add_devicebaytemplate",
|
|
1074
|
+
"dcim.add_modulebaytemplate",
|
|
1040
1075
|
)
|
|
1041
1076
|
|
|
1042
1077
|
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
@@ -1085,6 +1120,12 @@ device-bays:
|
|
|
1085
1120
|
self.assertEqual(dt.device_bay_templates.count(), 2)
|
|
1086
1121
|
# DeviceBayTemplate doesn't have a type field.
|
|
1087
1122
|
|
|
1123
|
+
self.assertEqual(dt.module_bay_templates.count(), 3)
|
|
1124
|
+
# ModuleBayTemplate doesn't have a type field.
|
|
1125
|
+
mbt = ModuleBayTemplate.objects.filter(device_type=dt).first()
|
|
1126
|
+
self.assertEqual(mbt.position, "1")
|
|
1127
|
+
self.assertEqual(mbt.name, "Module Bay 1")
|
|
1128
|
+
|
|
1088
1129
|
def test_devicetype_export(self):
|
|
1089
1130
|
url = reverse("dcim:devicetype_list")
|
|
1090
1131
|
self.add_permissions("dcim.view_devicetype")
|
|
@@ -1135,128 +1176,504 @@ device-bays:
|
|
|
1135
1176
|
self.assertIn("failed validation", response.content.decode(response.charset))
|
|
1136
1177
|
|
|
1137
1178
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1179
|
+
class ModuleTypeTestCase(
|
|
1180
|
+
ViewTestCases.GetObjectViewTestCase,
|
|
1181
|
+
ViewTestCases.GetObjectChangelogViewTestCase,
|
|
1182
|
+
ViewTestCases.CreateObjectViewTestCase,
|
|
1183
|
+
ViewTestCases.EditObjectViewTestCase,
|
|
1184
|
+
ViewTestCases.DeleteObjectViewTestCase,
|
|
1185
|
+
ViewTestCases.ListObjectsViewTestCase,
|
|
1186
|
+
ViewTestCases.BulkEditObjectsViewTestCase,
|
|
1187
|
+
ViewTestCases.BulkDeleteObjectsViewTestCase,
|
|
1188
|
+
):
|
|
1189
|
+
model = ModuleType
|
|
1145
1190
|
|
|
1146
1191
|
@classmethod
|
|
1147
1192
|
def setUpTestData(cls):
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1152
|
-
)
|
|
1193
|
+
manufacturers = Manufacturer.objects.all()[:2]
|
|
1194
|
+
Module.objects.all().delete()
|
|
1195
|
+
ModuleType.objects.all().delete()
|
|
1153
1196
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1197
|
+
ModuleType.objects.create(
|
|
1198
|
+
model="Test Module Type 1",
|
|
1199
|
+
manufacturer=manufacturers[0],
|
|
1200
|
+
comments="test comment",
|
|
1201
|
+
)
|
|
1202
|
+
ModuleType.objects.create(
|
|
1203
|
+
model="Test Module Type 2",
|
|
1204
|
+
manufacturer=manufacturers[0],
|
|
1205
|
+
)
|
|
1206
|
+
ModuleType.objects.create(
|
|
1207
|
+
model="Test Module Type 3",
|
|
1208
|
+
manufacturer=manufacturers[0],
|
|
1209
|
+
)
|
|
1210
|
+
ModuleType.objects.create(
|
|
1211
|
+
model="Test Module Type 4",
|
|
1212
|
+
manufacturer=manufacturers[1],
|
|
1213
|
+
)
|
|
1157
1214
|
|
|
1158
1215
|
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,
|
|
1216
|
+
"manufacturer": manufacturers[0].pk,
|
|
1217
|
+
"model": "Test Module Type X",
|
|
1218
|
+
"part_number": "123ABC",
|
|
1219
|
+
"tags": [t.pk for t in Tag.objects.get_for_model(ModuleType)],
|
|
1220
|
+
"comments": "test comment",
|
|
1168
1221
|
}
|
|
1169
1222
|
|
|
1170
1223
|
cls.bulk_edit_data = {
|
|
1171
|
-
"
|
|
1224
|
+
"manufacturer": manufacturers[1].pk,
|
|
1225
|
+
"comments": "changed comment",
|
|
1172
1226
|
}
|
|
1173
1227
|
|
|
1228
|
+
def test_list_has_correct_links(self):
|
|
1229
|
+
"""Assert that the ModuleType list view has import/export buttons for both CSV and YAML/JSON formats."""
|
|
1230
|
+
self.add_permissions("dcim.add_moduletype", "dcim.view_moduletype")
|
|
1231
|
+
response = self.client.get(reverse("dcim:moduletype_list"))
|
|
1232
|
+
self.assertHttpStatus(response, 200)
|
|
1233
|
+
content = extract_page_body(response.content.decode(response.charset))
|
|
1174
1234
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1235
|
+
yaml_import_url = reverse("dcim:moduletype_import")
|
|
1236
|
+
csv_import_url = job_import_url(ContentType.objects.get_for_model(ModuleType))
|
|
1237
|
+
# Dropdown provides both YAML/JSON and CSV import as options
|
|
1238
|
+
self.assertInHTML(
|
|
1239
|
+
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>',
|
|
1240
|
+
content,
|
|
1241
|
+
)
|
|
1242
|
+
self.assertInHTML(
|
|
1243
|
+
f'<a href="{csv_import_url}"><span class="mdi mdi-database-import text-muted" aria-hidden="true"></span> Import from CSV (multiple records)</a>',
|
|
1244
|
+
content,
|
|
1184
1245
|
)
|
|
1185
1246
|
|
|
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
|
|
1247
|
+
export_url = job_export_url()
|
|
1248
|
+
# Export is a little trickier to check since it's done as a form submission rather than an <a> element.
|
|
1249
|
+
self.assertIn(f'<form action="{export_url}" method="post">', content)
|
|
1250
|
+
self.assertInHTML(
|
|
1251
|
+
f'<input type="hidden" name="content_type" value="{ContentType.objects.get_for_model(self.model).pk}">',
|
|
1252
|
+
content,
|
|
1253
|
+
)
|
|
1254
|
+
self.assertInHTML('<input type="hidden" name="export_format" value="yaml">', content)
|
|
1255
|
+
self.assertInHTML(
|
|
1256
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as YAML</button>',
|
|
1257
|
+
content,
|
|
1258
|
+
)
|
|
1259
|
+
self.assertInHTML(
|
|
1260
|
+
'<button type="submit"><span class="mdi mdi-database-export text-muted" aria-hidden="true"></span> Export as CSV</button>',
|
|
1261
|
+
content,
|
|
1262
|
+
)
|
|
1209
1263
|
|
|
1210
|
-
@
|
|
1211
|
-
def
|
|
1264
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1265
|
+
def test_import_objects(self):
|
|
1266
|
+
"""
|
|
1267
|
+
Custom import test for YAML-based imports (versus CSV)
|
|
1268
|
+
"""
|
|
1269
|
+
# Note use of "power-outlets.power_port" (not "power_port_template") and "front-ports.rear_port"
|
|
1270
|
+
# (not "rear_port_template"). Note also inclusion of "slug" even though we removed DeviceType.slug in 2.0.
|
|
1271
|
+
# This is intentional as we are testing backwards compatibility with the netbox/devicetype-library repository.
|
|
1212
1272
|
manufacturer = Manufacturer.objects.first()
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1273
|
+
IMPORT_DATA = f"""
|
|
1274
|
+
manufacturer: {manufacturer.name}
|
|
1275
|
+
model: TEST-1000
|
|
1276
|
+
slug: test-1000
|
|
1277
|
+
console-ports:
|
|
1278
|
+
- name: Console Port 1
|
|
1279
|
+
type: de-9
|
|
1280
|
+
- name: Console Port 2
|
|
1281
|
+
type: de-9
|
|
1282
|
+
- name: Console Port 3
|
|
1283
|
+
type: de-9
|
|
1284
|
+
console-server-ports:
|
|
1285
|
+
- name: Console Server Port 1
|
|
1286
|
+
type: rj-45
|
|
1287
|
+
- name: Console Server Port 2
|
|
1288
|
+
type: rj-45
|
|
1289
|
+
- name: Console Server Port 3
|
|
1290
|
+
type: rj-45
|
|
1291
|
+
power-ports:
|
|
1292
|
+
- name: Power Port 1
|
|
1293
|
+
type: iec-60320-c14
|
|
1294
|
+
- name: Power Port 2
|
|
1295
|
+
type: iec-60320-c14
|
|
1296
|
+
- name: Power Port 3
|
|
1297
|
+
type: iec-60320-c14
|
|
1298
|
+
power-outlets:
|
|
1299
|
+
- name: Power Outlet 1
|
|
1300
|
+
type: iec-60320-c13
|
|
1301
|
+
power_port: Power Port 1
|
|
1302
|
+
feed_leg: A
|
|
1303
|
+
- name: Power Outlet 2
|
|
1304
|
+
type: iec-60320-c13
|
|
1305
|
+
power_port: Power Port 1
|
|
1306
|
+
feed_leg: A
|
|
1307
|
+
- name: Power Outlet 3
|
|
1308
|
+
type: iec-60320-c13
|
|
1309
|
+
power_port: Power Port 1
|
|
1310
|
+
feed_leg: A
|
|
1311
|
+
interfaces:
|
|
1312
|
+
- name: Interface 1
|
|
1313
|
+
type: 1000base-t
|
|
1314
|
+
mgmt_only: true
|
|
1315
|
+
- name: Interface 2
|
|
1316
|
+
type: 1000base-t
|
|
1317
|
+
- name: Interface 3
|
|
1318
|
+
type: 1000base-t
|
|
1319
|
+
rear-ports:
|
|
1320
|
+
- name: Rear Port 1
|
|
1321
|
+
type: 8p8c
|
|
1322
|
+
- name: Rear Port 2
|
|
1323
|
+
type: 8p8c
|
|
1324
|
+
- name: Rear Port 3
|
|
1325
|
+
type: 8p8c
|
|
1326
|
+
front-ports:
|
|
1327
|
+
- name: Front Port 1
|
|
1328
|
+
type: 8p8c
|
|
1329
|
+
rear_port: Rear Port 1
|
|
1330
|
+
- name: Front Port 2
|
|
1331
|
+
type: 8p8c
|
|
1332
|
+
rear_port: Rear Port 2
|
|
1333
|
+
- name: Front Port 3
|
|
1334
|
+
type: 8p8c
|
|
1335
|
+
rear_port: Rear Port 3
|
|
1336
|
+
module-bays:
|
|
1337
|
+
- name: Module Bay 1
|
|
1338
|
+
position: 1
|
|
1339
|
+
- name: Module Bay 2
|
|
1340
|
+
position: 2
|
|
1341
|
+
- name: Module Bay 3
|
|
1342
|
+
position: 3
|
|
1343
|
+
"""
|
|
1344
|
+
|
|
1345
|
+
# Add all required permissions to the test user
|
|
1346
|
+
self.add_permissions(
|
|
1347
|
+
"dcim.view_moduletype",
|
|
1348
|
+
"dcim.add_moduletype",
|
|
1349
|
+
"dcim.add_consoleporttemplate",
|
|
1350
|
+
"dcim.add_consoleserverporttemplate",
|
|
1351
|
+
"dcim.add_powerporttemplate",
|
|
1352
|
+
"dcim.add_poweroutlettemplate",
|
|
1353
|
+
"dcim.add_interfacetemplate",
|
|
1354
|
+
"dcim.add_frontporttemplate",
|
|
1355
|
+
"dcim.add_rearporttemplate",
|
|
1356
|
+
"dcim.add_modulebaytemplate",
|
|
1216
1357
|
)
|
|
1217
1358
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1359
|
+
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
1360
|
+
response = self.client.post(reverse("dcim:moduletype_import"), data=form_data, follow=True)
|
|
1361
|
+
self.assertHttpStatus(response, 200)
|
|
1362
|
+
mt = ModuleType.objects.get(model="TEST-1000")
|
|
1221
1363
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
"allocated_draw": 50,
|
|
1228
|
-
}
|
|
1364
|
+
# Verify all of the components were created
|
|
1365
|
+
self.assertEqual(mt.console_port_templates.count(), 3)
|
|
1366
|
+
cp1 = mt.console_port_templates.first()
|
|
1367
|
+
self.assertEqual(cp1.name, "Console Port 1")
|
|
1368
|
+
self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
|
|
1229
1369
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
"maximum_draw": 100,
|
|
1235
|
-
"allocated_draw": 50,
|
|
1236
|
-
}
|
|
1370
|
+
self.assertEqual(mt.console_server_port_templates.count(), 3)
|
|
1371
|
+
csp1 = mt.console_server_port_templates.first()
|
|
1372
|
+
self.assertEqual(csp1.name, "Console Server Port 1")
|
|
1373
|
+
self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
|
|
1237
1374
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
}
|
|
1375
|
+
self.assertEqual(mt.power_port_templates.count(), 3)
|
|
1376
|
+
pp1 = mt.power_port_templates.first()
|
|
1377
|
+
self.assertEqual(pp1.name, "Power Port 1")
|
|
1378
|
+
self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
|
|
1243
1379
|
|
|
1380
|
+
self.assertEqual(mt.power_outlet_templates.count(), 3)
|
|
1381
|
+
po1 = mt.power_outlet_templates.first()
|
|
1382
|
+
self.assertEqual(po1.name, "Power Outlet 1")
|
|
1383
|
+
self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
|
|
1384
|
+
self.assertEqual(po1.power_port_template, pp1)
|
|
1385
|
+
self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
|
|
1244
1386
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1387
|
+
self.assertEqual(mt.interface_templates.count(), 3)
|
|
1388
|
+
iface1 = mt.interface_templates.first()
|
|
1389
|
+
self.assertEqual(iface1.name, "Interface 1")
|
|
1390
|
+
self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
|
|
1391
|
+
self.assertTrue(iface1.mgmt_only)
|
|
1247
1392
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1393
|
+
self.assertEqual(mt.rear_port_templates.count(), 3)
|
|
1394
|
+
rp1 = mt.rear_port_templates.first()
|
|
1395
|
+
self.assertEqual(rp1.name, "Rear Port 1")
|
|
1252
1396
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1397
|
+
self.assertEqual(mt.front_port_templates.count(), 3)
|
|
1398
|
+
fp1 = mt.front_port_templates.first()
|
|
1399
|
+
self.assertEqual(fp1.name, "Front Port 1")
|
|
1400
|
+
self.assertEqual(fp1.rear_port_template, rp1)
|
|
1401
|
+
self.assertEqual(fp1.rear_port_position, 1)
|
|
1256
1402
|
|
|
1257
|
-
|
|
1403
|
+
self.assertEqual(mt.module_bay_templates.count(), 3)
|
|
1404
|
+
mb1 = mt.module_bay_templates.first()
|
|
1405
|
+
self.assertEqual(mb1.name, "Module Bay 1")
|
|
1406
|
+
self.assertEqual(mb1.position, "1")
|
|
1258
1407
|
|
|
1259
|
-
|
|
1408
|
+
def test_import_objects_unknown_type_enums(self):
|
|
1409
|
+
"""
|
|
1410
|
+
YAML import of data with `type` values that we don't recognize should remap those to "other" rather than fail.
|
|
1411
|
+
"""
|
|
1412
|
+
manufacturer = Manufacturer.objects.first()
|
|
1413
|
+
IMPORT_DATA = f"""
|
|
1414
|
+
manufacturer: {manufacturer.name}
|
|
1415
|
+
model: TEST-2000
|
|
1416
|
+
console-ports:
|
|
1417
|
+
- name: Console Port Alpha-Beta
|
|
1418
|
+
type: alpha-beta
|
|
1419
|
+
console-server-ports:
|
|
1420
|
+
- name: Console Server Port Pineapple
|
|
1421
|
+
type: pineapple
|
|
1422
|
+
power-ports:
|
|
1423
|
+
- name: Power Port Fred
|
|
1424
|
+
type: frederick
|
|
1425
|
+
power-outlets:
|
|
1426
|
+
- name: Power Outlet Rick
|
|
1427
|
+
type: frederick
|
|
1428
|
+
power_port_template: Power Port Fred
|
|
1429
|
+
interfaces:
|
|
1430
|
+
- name: Interface North
|
|
1431
|
+
type: northern
|
|
1432
|
+
rear-ports:
|
|
1433
|
+
- name: Rear Port Foosball
|
|
1434
|
+
type: foosball
|
|
1435
|
+
front-ports:
|
|
1436
|
+
- name: Front Port Pickleball
|
|
1437
|
+
type: pickleball
|
|
1438
|
+
rear_port_template: Rear Port Foosball
|
|
1439
|
+
module-bays:
|
|
1440
|
+
- name: Module Bay 1
|
|
1441
|
+
position: 1
|
|
1442
|
+
- name: Module Bay 2
|
|
1443
|
+
position: 2
|
|
1444
|
+
- name: Module Bay 3
|
|
1445
|
+
position: 3
|
|
1446
|
+
"""
|
|
1447
|
+
# Add all required permissions to the test user
|
|
1448
|
+
self.add_permissions(
|
|
1449
|
+
"dcim.view_moduletype",
|
|
1450
|
+
"dcim.view_manufacturer",
|
|
1451
|
+
"dcim.add_moduletype",
|
|
1452
|
+
"dcim.add_consoleporttemplate",
|
|
1453
|
+
"dcim.add_consoleserverporttemplate",
|
|
1454
|
+
"dcim.add_powerporttemplate",
|
|
1455
|
+
"dcim.add_poweroutlettemplate",
|
|
1456
|
+
"dcim.add_interfacetemplate",
|
|
1457
|
+
"dcim.add_frontporttemplate",
|
|
1458
|
+
"dcim.add_rearporttemplate",
|
|
1459
|
+
"dcim.add_modulebaytemplate",
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
form_data = {"data": IMPORT_DATA, "format": "yaml"}
|
|
1463
|
+
response = self.client.post(reverse("dcim:moduletype_import"), data=form_data, follow=True)
|
|
1464
|
+
self.assertHttpStatus(response, 200)
|
|
1465
|
+
mt = ModuleType.objects.get(model="TEST-2000")
|
|
1466
|
+
|
|
1467
|
+
# Verify all of the components were created with appropriate "other" types
|
|
1468
|
+
self.assertEqual(mt.console_port_templates.count(), 1)
|
|
1469
|
+
cpt = ConsolePortTemplate.objects.filter(module_type=mt).first()
|
|
1470
|
+
self.assertEqual(cpt.name, "Console Port Alpha-Beta")
|
|
1471
|
+
self.assertEqual(cpt.type, ConsolePortTypeChoices.TYPE_OTHER)
|
|
1472
|
+
|
|
1473
|
+
self.assertEqual(mt.console_server_port_templates.count(), 1)
|
|
1474
|
+
cspt = ConsoleServerPortTemplate.objects.filter(module_type=mt).first()
|
|
1475
|
+
self.assertEqual(cspt.name, "Console Server Port Pineapple")
|
|
1476
|
+
self.assertEqual(cspt.type, ConsolePortTypeChoices.TYPE_OTHER)
|
|
1477
|
+
|
|
1478
|
+
self.assertEqual(mt.power_port_templates.count(), 1)
|
|
1479
|
+
ppt = PowerPortTemplate.objects.filter(module_type=mt).first()
|
|
1480
|
+
self.assertEqual(ppt.name, "Power Port Fred")
|
|
1481
|
+
self.assertEqual(ppt.type, PowerPortTypeChoices.TYPE_OTHER)
|
|
1482
|
+
|
|
1483
|
+
self.assertEqual(mt.power_outlet_templates.count(), 1)
|
|
1484
|
+
pot = PowerOutletTemplate.objects.filter(module_type=mt).first()
|
|
1485
|
+
self.assertEqual(pot.name, "Power Outlet Rick")
|
|
1486
|
+
self.assertEqual(pot.type, PowerOutletTypeChoices.TYPE_OTHER)
|
|
1487
|
+
self.assertEqual(pot.power_port_template, ppt)
|
|
1488
|
+
|
|
1489
|
+
self.assertEqual(mt.interface_templates.count(), 1)
|
|
1490
|
+
it = InterfaceTemplate.objects.filter(module_type=mt).first()
|
|
1491
|
+
self.assertEqual(it.name, "Interface North")
|
|
1492
|
+
self.assertEqual(it.type, InterfaceTypeChoices.TYPE_OTHER)
|
|
1493
|
+
|
|
1494
|
+
self.assertEqual(mt.rear_port_templates.count(), 1)
|
|
1495
|
+
rpt = RearPortTemplate.objects.filter(module_type=mt).first()
|
|
1496
|
+
self.assertEqual(rpt.name, "Rear Port Foosball")
|
|
1497
|
+
self.assertEqual(rpt.type, PortTypeChoices.TYPE_OTHER)
|
|
1498
|
+
|
|
1499
|
+
self.assertEqual(mt.front_port_templates.count(), 1)
|
|
1500
|
+
fpt = FrontPortTemplate.objects.filter(module_type=mt).first()
|
|
1501
|
+
self.assertEqual(fpt.name, "Front Port Pickleball")
|
|
1502
|
+
self.assertEqual(fpt.type, PortTypeChoices.TYPE_OTHER)
|
|
1503
|
+
|
|
1504
|
+
self.assertEqual(mt.module_bay_templates.count(), 3)
|
|
1505
|
+
# ModuleBayTemplate doesn't have a type field.
|
|
1506
|
+
mbt = ModuleBayTemplate.objects.filter(module_type=mt).first()
|
|
1507
|
+
self.assertEqual(mbt.position, "1")
|
|
1508
|
+
self.assertEqual(mbt.name, "Module Bay 1")
|
|
1509
|
+
|
|
1510
|
+
def test_moduletype_export(self):
|
|
1511
|
+
url = reverse("dcim:moduletype_list")
|
|
1512
|
+
self.add_permissions("dcim.view_moduletype")
|
|
1513
|
+
|
|
1514
|
+
response = self.client.get(f"{url}?export")
|
|
1515
|
+
self.assertEqual(response.status_code, 200)
|
|
1516
|
+
data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
|
|
1517
|
+
module_types = ModuleType.objects.all()
|
|
1518
|
+
module_type = module_types.first()
|
|
1519
|
+
|
|
1520
|
+
self.assertEqual(len(data), module_types.count())
|
|
1521
|
+
self.assertEqual(data[0]["manufacturer"], module_type.manufacturer.name)
|
|
1522
|
+
self.assertEqual(data[0]["model"], module_type.model)
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
#
|
|
1526
|
+
# DeviceType components
|
|
1527
|
+
#
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1531
|
+
model = ConsolePortTemplate
|
|
1532
|
+
|
|
1533
|
+
@classmethod
|
|
1534
|
+
def setUpTestData(cls):
|
|
1535
|
+
manufacturer = Manufacturer.objects.first()
|
|
1536
|
+
devicetypes = (
|
|
1537
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1538
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1539
|
+
)
|
|
1540
|
+
|
|
1541
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 1")
|
|
1542
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 2")
|
|
1543
|
+
ConsolePortTemplate.objects.create(device_type=devicetypes[0], name="Console Port Template 3")
|
|
1544
|
+
|
|
1545
|
+
cls.form_data = {
|
|
1546
|
+
"device_type": devicetypes[1].pk,
|
|
1547
|
+
"name": "Console Port Template X",
|
|
1548
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
cls.bulk_create_data = {
|
|
1552
|
+
"device_type": devicetypes[1].pk,
|
|
1553
|
+
"name_pattern": "Console Port Template [4-6]",
|
|
1554
|
+
"description": "View Test Bulk Create Console Ports",
|
|
1555
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
cls.bulk_edit_data = {
|
|
1559
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
test_instance = cls.model.objects.first()
|
|
1563
|
+
cls.update_data = {
|
|
1564
|
+
"name": test_instance.name,
|
|
1565
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1566
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1567
|
+
"label": "new test label",
|
|
1568
|
+
"description": "new test description",
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1573
|
+
model = ConsoleServerPortTemplate
|
|
1574
|
+
|
|
1575
|
+
@classmethod
|
|
1576
|
+
def setUpTestData(cls):
|
|
1577
|
+
manufacturer = Manufacturer.objects.first()
|
|
1578
|
+
devicetypes = (
|
|
1579
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1580
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1581
|
+
)
|
|
1582
|
+
|
|
1583
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 1")
|
|
1584
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 2")
|
|
1585
|
+
ConsoleServerPortTemplate.objects.create(device_type=devicetypes[0], name="Console Server Port Template 3")
|
|
1586
|
+
|
|
1587
|
+
cls.form_data = {
|
|
1588
|
+
"device_type": devicetypes[1].pk,
|
|
1589
|
+
"name": "Console Server Port Template X",
|
|
1590
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
cls.bulk_create_data = {
|
|
1594
|
+
"device_type": devicetypes[1].pk,
|
|
1595
|
+
"name_pattern": "Console Server Port Template [4-6]",
|
|
1596
|
+
"description": "View Test Bulk Create Console Server Ports",
|
|
1597
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
cls.bulk_edit_data = {
|
|
1601
|
+
"type": ConsolePortTypeChoices.TYPE_RJ45,
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
test_instance = cls.model.objects.first()
|
|
1605
|
+
cls.update_data = {
|
|
1606
|
+
"name": test_instance.name,
|
|
1607
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1608
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1609
|
+
"label": "new test label",
|
|
1610
|
+
"description": "new test description",
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1615
|
+
model = PowerPortTemplate
|
|
1616
|
+
|
|
1617
|
+
@classmethod
|
|
1618
|
+
def setUpTestData(cls):
|
|
1619
|
+
manufacturer = Manufacturer.objects.first()
|
|
1620
|
+
devicetypes = (
|
|
1621
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1"),
|
|
1622
|
+
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 1")
|
|
1626
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 2")
|
|
1627
|
+
PowerPortTemplate.objects.create(device_type=devicetypes[0], name="Power Port Template 3")
|
|
1628
|
+
|
|
1629
|
+
cls.form_data = {
|
|
1630
|
+
"device_type": devicetypes[1].pk,
|
|
1631
|
+
"name": "Power Port Template X",
|
|
1632
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1633
|
+
"maximum_draw": 100,
|
|
1634
|
+
"allocated_draw": 50,
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
cls.bulk_create_data = {
|
|
1638
|
+
"device_type": devicetypes[1].pk,
|
|
1639
|
+
"name_pattern": "Power Port Template [4-6]",
|
|
1640
|
+
"description": "View Test Bulk Create Power Ports",
|
|
1641
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1642
|
+
"maximum_draw": 100,
|
|
1643
|
+
"allocated_draw": 50,
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
cls.bulk_edit_data = {
|
|
1647
|
+
"type": PowerPortTypeChoices.TYPE_IEC_C14,
|
|
1648
|
+
"maximum_draw": 100,
|
|
1649
|
+
"allocated_draw": 50,
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
test_instance = cls.model.objects.first()
|
|
1653
|
+
cls.update_data = {
|
|
1654
|
+
"name": test_instance.name,
|
|
1655
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1656
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1657
|
+
"label": "new test label",
|
|
1658
|
+
"description": "new test description",
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1663
|
+
model = PowerOutletTemplate
|
|
1664
|
+
|
|
1665
|
+
@classmethod
|
|
1666
|
+
def setUpTestData(cls):
|
|
1667
|
+
manufacturer = Manufacturer.objects.first()
|
|
1668
|
+
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1669
|
+
|
|
1670
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 1")
|
|
1671
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 2")
|
|
1672
|
+
PowerOutletTemplate.objects.create(device_type=devicetype, name="Power Outlet Template 3")
|
|
1673
|
+
|
|
1674
|
+
powerports = (PowerPortTemplate.objects.create(device_type=devicetype, name="Power Port Template 1"),)
|
|
1675
|
+
|
|
1676
|
+
cls.form_data = {
|
|
1260
1677
|
"device_type": devicetype.pk,
|
|
1261
1678
|
"name": "Power Outlet Template X",
|
|
1262
1679
|
"type": PowerOutletTypeChoices.TYPE_IEC_C13,
|
|
@@ -1267,6 +1684,7 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|
|
1267
1684
|
cls.bulk_create_data = {
|
|
1268
1685
|
"device_type": devicetype.pk,
|
|
1269
1686
|
"name_pattern": "Power Outlet Template [4-6]",
|
|
1687
|
+
"description": "View Test Bulk Create Power Outlets",
|
|
1270
1688
|
"type": PowerOutletTypeChoices.TYPE_IEC_C13,
|
|
1271
1689
|
"power_port_template": powerports[0].pk,
|
|
1272
1690
|
"feed_leg": PowerOutletFeedLegChoices.FEED_LEG_B,
|
|
@@ -1277,6 +1695,17 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|
|
1277
1695
|
"feed_leg": PowerOutletFeedLegChoices.FEED_LEG_B,
|
|
1278
1696
|
}
|
|
1279
1697
|
|
|
1698
|
+
test_instance = cls.model.objects.first()
|
|
1699
|
+
cls.update_data = {
|
|
1700
|
+
"name": test_instance.name,
|
|
1701
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1702
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1703
|
+
# power_port_template must match the parent device/module type
|
|
1704
|
+
"power_port_template": getattr(test_instance.power_port_template, "pk", None),
|
|
1705
|
+
"label": "new test label",
|
|
1706
|
+
"description": "new test description",
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1280
1709
|
|
|
1281
1710
|
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1282
1711
|
model = InterfaceTemplate
|
|
@@ -1289,9 +1718,21 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1289
1718
|
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1290
1719
|
)
|
|
1291
1720
|
|
|
1292
|
-
InterfaceTemplate.objects.create(
|
|
1293
|
-
|
|
1294
|
-
|
|
1721
|
+
InterfaceTemplate.objects.create(
|
|
1722
|
+
device_type=devicetypes[0],
|
|
1723
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1724
|
+
name="Interface Template 1",
|
|
1725
|
+
)
|
|
1726
|
+
InterfaceTemplate.objects.create(
|
|
1727
|
+
device_type=devicetypes[0],
|
|
1728
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1729
|
+
name="Interface Template 2",
|
|
1730
|
+
)
|
|
1731
|
+
InterfaceTemplate.objects.create(
|
|
1732
|
+
device_type=devicetypes[0],
|
|
1733
|
+
type=InterfaceTypeChoices.TYPE_100GE_QSFP_DD,
|
|
1734
|
+
name="Interface Template 3",
|
|
1735
|
+
)
|
|
1295
1736
|
|
|
1296
1737
|
cls.form_data = {
|
|
1297
1738
|
"device_type": devicetypes[1].pk,
|
|
@@ -1305,6 +1746,7 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1305
1746
|
"name_pattern": "Interface Template [4-6]",
|
|
1306
1747
|
# Test that a label can be applied to each generated interface templates
|
|
1307
1748
|
"label_pattern": "Interface Template Label [3-5]",
|
|
1749
|
+
"description": "View Test Bulk Create Interfaces",
|
|
1308
1750
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
1309
1751
|
"mgmt_only": True,
|
|
1310
1752
|
}
|
|
@@ -1314,6 +1756,16 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1314
1756
|
"mgmt_only": True,
|
|
1315
1757
|
}
|
|
1316
1758
|
|
|
1759
|
+
test_instance = cls.model.objects.first()
|
|
1760
|
+
cls.update_data = {
|
|
1761
|
+
"name": test_instance.name,
|
|
1762
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1763
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1764
|
+
"type": test_instance.type,
|
|
1765
|
+
"label": "new test label",
|
|
1766
|
+
"description": "new test description",
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1317
1769
|
|
|
1318
1770
|
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1319
1771
|
model = FrontPortTemplate
|
|
@@ -1324,29 +1776,62 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1324
1776
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1")
|
|
1325
1777
|
|
|
1326
1778
|
rearports = (
|
|
1327
|
-
RearPortTemplate.objects.create(
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1779
|
+
RearPortTemplate.objects.create(
|
|
1780
|
+
device_type=devicetype,
|
|
1781
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1782
|
+
positions=24,
|
|
1783
|
+
name="Rear Port Template 1",
|
|
1784
|
+
),
|
|
1785
|
+
RearPortTemplate.objects.create(
|
|
1786
|
+
device_type=devicetype,
|
|
1787
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1788
|
+
positions=24,
|
|
1789
|
+
name="Rear Port Template 2",
|
|
1790
|
+
),
|
|
1791
|
+
RearPortTemplate.objects.create(
|
|
1792
|
+
device_type=devicetype,
|
|
1793
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1794
|
+
positions=24,
|
|
1795
|
+
name="Rear Port Template 3",
|
|
1796
|
+
),
|
|
1797
|
+
RearPortTemplate.objects.create(
|
|
1798
|
+
device_type=devicetype,
|
|
1799
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1800
|
+
positions=24,
|
|
1801
|
+
name="Rear Port Template 4",
|
|
1802
|
+
),
|
|
1803
|
+
RearPortTemplate.objects.create(
|
|
1804
|
+
device_type=devicetype,
|
|
1805
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1806
|
+
positions=24,
|
|
1807
|
+
name="Rear Port Template 5",
|
|
1808
|
+
),
|
|
1809
|
+
RearPortTemplate.objects.create(
|
|
1810
|
+
device_type=devicetype,
|
|
1811
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1812
|
+
positions=24,
|
|
1813
|
+
name="Rear Port Template 6",
|
|
1814
|
+
),
|
|
1333
1815
|
)
|
|
1334
1816
|
|
|
1335
1817
|
FrontPortTemplate.objects.create(
|
|
1336
1818
|
device_type=devicetype,
|
|
1337
|
-
name="Front Port Template 1",
|
|
1819
|
+
name="View Test Front Port Template 1",
|
|
1820
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1338
1821
|
rear_port_template=rearports[0],
|
|
1339
1822
|
rear_port_position=1,
|
|
1340
1823
|
)
|
|
1341
1824
|
FrontPortTemplate.objects.create(
|
|
1342
1825
|
device_type=devicetype,
|
|
1343
|
-
name="Front Port Template 2",
|
|
1826
|
+
name="View Test Front Port Template 2",
|
|
1827
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1344
1828
|
rear_port_template=rearports[1],
|
|
1345
1829
|
rear_port_position=1,
|
|
1346
1830
|
)
|
|
1347
1831
|
FrontPortTemplate.objects.create(
|
|
1348
1832
|
device_type=devicetype,
|
|
1349
|
-
name="Front Port Template 3",
|
|
1833
|
+
name="View Test Front Port Template 3",
|
|
1834
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1350
1835
|
rear_port_template=rearports[2],
|
|
1351
1836
|
rear_port_position=1,
|
|
1352
1837
|
)
|
|
@@ -1361,13 +1846,26 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1361
1846
|
|
|
1362
1847
|
cls.bulk_create_data = {
|
|
1363
1848
|
"device_type": devicetype.pk,
|
|
1364
|
-
"name_pattern": "Front Port [4-6]",
|
|
1849
|
+
"name_pattern": "View Test Front Port [4-6]",
|
|
1850
|
+
"description": "View Test Bulk Create Front Ports",
|
|
1365
1851
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1366
1852
|
"rear_port_template_set": [f"{rp.pk}:1" for rp in rearports[3:6]],
|
|
1367
1853
|
}
|
|
1368
1854
|
|
|
1369
1855
|
cls.bulk_edit_data = {
|
|
1370
|
-
"type": PortTypeChoices.
|
|
1856
|
+
"type": PortTypeChoices.TYPE_4P4C,
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
test_instance = cls.model.objects.first()
|
|
1860
|
+
cls.update_data = {
|
|
1861
|
+
"name": test_instance.name,
|
|
1862
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1863
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1864
|
+
"rear_port_template": test_instance.rear_port_template.pk,
|
|
1865
|
+
"rear_port_position": test_instance.rear_port_position,
|
|
1866
|
+
"type": test_instance.type,
|
|
1867
|
+
"label": "new test label",
|
|
1868
|
+
"description": "new test description",
|
|
1371
1869
|
}
|
|
1372
1870
|
|
|
1373
1871
|
|
|
@@ -1382,9 +1880,24 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1382
1880
|
DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 2"),
|
|
1383
1881
|
)
|
|
1384
1882
|
|
|
1385
|
-
RearPortTemplate.objects.create(
|
|
1386
|
-
|
|
1387
|
-
|
|
1883
|
+
RearPortTemplate.objects.create(
|
|
1884
|
+
device_type=devicetypes[0],
|
|
1885
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1886
|
+
positions=24,
|
|
1887
|
+
name="Rear Port Template 1",
|
|
1888
|
+
)
|
|
1889
|
+
RearPortTemplate.objects.create(
|
|
1890
|
+
device_type=devicetypes[0],
|
|
1891
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1892
|
+
positions=24,
|
|
1893
|
+
name="Rear Port Template 2",
|
|
1894
|
+
)
|
|
1895
|
+
RearPortTemplate.objects.create(
|
|
1896
|
+
device_type=devicetypes[0],
|
|
1897
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
1898
|
+
positions=24,
|
|
1899
|
+
name="Rear Port Template 3",
|
|
1900
|
+
)
|
|
1388
1901
|
|
|
1389
1902
|
cls.form_data = {
|
|
1390
1903
|
"device_type": devicetypes[1].pk,
|
|
@@ -1396,6 +1909,7 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1396
1909
|
cls.bulk_create_data = {
|
|
1397
1910
|
"device_type": devicetypes[1].pk,
|
|
1398
1911
|
"name_pattern": "Rear Port Template [4-6]",
|
|
1912
|
+
"description": "View Test Bulk Create Rear Ports",
|
|
1399
1913
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1400
1914
|
"positions": 2,
|
|
1401
1915
|
}
|
|
@@ -1404,6 +1918,17 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|
|
1404
1918
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1405
1919
|
}
|
|
1406
1920
|
|
|
1921
|
+
test_instance = cls.model.objects.first()
|
|
1922
|
+
cls.update_data = {
|
|
1923
|
+
"name": test_instance.name,
|
|
1924
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
1925
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
1926
|
+
"positions": test_instance.positions,
|
|
1927
|
+
"type": test_instance.type,
|
|
1928
|
+
"label": "new test label",
|
|
1929
|
+
"description": "new test description",
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1407
1932
|
|
|
1408
1933
|
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1409
1934
|
model = DeviceBayTemplate
|
|
@@ -1436,31 +1961,80 @@ class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|
|
1436
1961
|
cls.bulk_create_data = {
|
|
1437
1962
|
"device_type": devicetypes[1].pk,
|
|
1438
1963
|
"name_pattern": "Device Bay Template [4-6]",
|
|
1964
|
+
"description": "View Test Bulk Create Device Bays",
|
|
1439
1965
|
}
|
|
1440
1966
|
|
|
1441
1967
|
cls.bulk_edit_data = {
|
|
1442
1968
|
"description": "Foo bar",
|
|
1443
1969
|
}
|
|
1444
1970
|
|
|
1971
|
+
test_instance = cls.model.objects.first()
|
|
1972
|
+
cls.update_data = {
|
|
1973
|
+
"name": test_instance.name,
|
|
1974
|
+
"device_type": test_instance.device_type.pk,
|
|
1975
|
+
"label": "new test label",
|
|
1976
|
+
"description": "new test description",
|
|
1977
|
+
}
|
|
1445
1978
|
|
|
1446
|
-
|
|
1447
|
-
|
|
1979
|
+
|
|
1980
|
+
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
|
1981
|
+
model = ModuleBayTemplate
|
|
1448
1982
|
|
|
1449
1983
|
@classmethod
|
|
1450
1984
|
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)
|
|
1985
|
+
device_type = DeviceType.objects.first()
|
|
1986
|
+
module_type = ModuleType.objects.first()
|
|
1457
1987
|
|
|
1458
1988
|
cls.form_data = {
|
|
1459
|
-
"
|
|
1460
|
-
"
|
|
1461
|
-
"
|
|
1462
|
-
"
|
|
1463
|
-
"
|
|
1989
|
+
"device_type": device_type.pk,
|
|
1990
|
+
"module_type": None,
|
|
1991
|
+
"name": "Module Bay Template X",
|
|
1992
|
+
"position": "Test modulebaytemplate position",
|
|
1993
|
+
"description": "Test modulebaytemplate description",
|
|
1994
|
+
"label": "Test modulebaytemplate label",
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
cls.bulk_create_data = {
|
|
1998
|
+
"module_type": module_type.pk,
|
|
1999
|
+
"name_pattern": "Test Module Bay Template [5-7]",
|
|
2000
|
+
"position_pattern": "Test Module Bay Template Position [10-12]",
|
|
2001
|
+
"label_pattern": "Test modulebaytemplate label [1-3]",
|
|
2002
|
+
"description": "Test modulebaytemplate description",
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
cls.bulk_edit_data = {
|
|
2006
|
+
"description": "Description changed",
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
test_instance = cls.model.objects.first()
|
|
2010
|
+
cls.update_data = {
|
|
2011
|
+
"name": test_instance.name,
|
|
2012
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
2013
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
2014
|
+
"position": "new test position",
|
|
2015
|
+
"label": "new test label",
|
|
2016
|
+
"description": "new test description",
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
|
|
2020
|
+
class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
2021
|
+
model = Platform
|
|
2022
|
+
|
|
2023
|
+
@classmethod
|
|
2024
|
+
def setUpTestData(cls):
|
|
2025
|
+
manufacturer = Manufacturer.objects.first()
|
|
2026
|
+
|
|
2027
|
+
# Protected FK to SoftwareImageFile prevents deletion
|
|
2028
|
+
DeviceTypeToSoftwareImageFile.objects.all().delete()
|
|
2029
|
+
# Protected FK to SoftwareVersion prevents deletion
|
|
2030
|
+
Device.objects.all().update(software_version=None)
|
|
2031
|
+
|
|
2032
|
+
cls.form_data = {
|
|
2033
|
+
"name": "Platform X",
|
|
2034
|
+
"manufacturer": manufacturer.pk,
|
|
2035
|
+
"napalm_driver": "junos",
|
|
2036
|
+
"napalm_args": None,
|
|
2037
|
+
"network_driver": "juniper_junos",
|
|
1464
2038
|
"description": "A new platform",
|
|
1465
2039
|
}
|
|
1466
2040
|
|
|
@@ -1593,11 +2167,11 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1593
2167
|
)
|
|
1594
2168
|
|
|
1595
2169
|
intf_status = Status.objects.get_for_model(Interface).first()
|
|
1596
|
-
|
|
2170
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
1597
2171
|
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
|
|
2172
|
+
Interface.objects.create(device=devices[0], name="Interface A1", status=intf_status, role=intf_role),
|
|
2173
|
+
Interface.objects.create(device=devices[0], name="Interface A2", status=intf_status),
|
|
2174
|
+
Interface.objects.create(device=devices[0], name="Interface A3", status=intf_status, role=intf_role),
|
|
1601
2175
|
)
|
|
1602
2176
|
|
|
1603
2177
|
for device, ipaddress in zip(devices, ipaddresses):
|
|
@@ -1654,52 +2228,456 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1654
2228
|
def test_device_consoleports(self):
|
|
1655
2229
|
device = Device.objects.first()
|
|
1656
2230
|
|
|
1657
|
-
ConsolePort.objects.create(device=device, name="Console Port 1")
|
|
1658
|
-
ConsolePort.objects.create(device=device, name="Console Port 2")
|
|
1659
|
-
ConsolePort.objects.create(device=device, name="Console Port 3")
|
|
2231
|
+
ConsolePort.objects.create(device=device, name="Console Port 1")
|
|
2232
|
+
ConsolePort.objects.create(device=device, name="Console Port 2")
|
|
2233
|
+
ConsolePort.objects.create(device=device, name="Console Port 3")
|
|
2234
|
+
|
|
2235
|
+
url = reverse("dcim:device_consoleports", kwargs={"pk": device.pk})
|
|
2236
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2237
|
+
|
|
2238
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2239
|
+
def test_device_consoleserverports(self):
|
|
2240
|
+
device = Device.objects.first()
|
|
2241
|
+
|
|
2242
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 1")
|
|
2243
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 2")
|
|
2244
|
+
ConsoleServerPort.objects.create(device=device, name="Console Server Port 3")
|
|
2245
|
+
|
|
2246
|
+
url = reverse("dcim:device_consoleserverports", kwargs={"pk": device.pk})
|
|
2247
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2248
|
+
|
|
2249
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2250
|
+
def test_device_powerports(self):
|
|
2251
|
+
device = Device.objects.first()
|
|
2252
|
+
|
|
2253
|
+
PowerPort.objects.create(device=device, name="Power Port 1")
|
|
2254
|
+
PowerPort.objects.create(device=device, name="Power Port 2")
|
|
2255
|
+
PowerPort.objects.create(device=device, name="Power Port 3")
|
|
2256
|
+
|
|
2257
|
+
url = reverse("dcim:device_powerports", kwargs={"pk": device.pk})
|
|
2258
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2259
|
+
|
|
2260
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2261
|
+
def test_device_poweroutlets(self):
|
|
2262
|
+
device = Device.objects.first()
|
|
2263
|
+
|
|
2264
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 1")
|
|
2265
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 2")
|
|
2266
|
+
PowerOutlet.objects.create(device=device, name="Power Outlet 3")
|
|
2267
|
+
|
|
2268
|
+
url = reverse("dcim:device_poweroutlets", kwargs={"pk": device.pk})
|
|
2269
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2270
|
+
|
|
2271
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2272
|
+
def test_device_interfaces(self):
|
|
2273
|
+
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
2274
|
+
self.add_permissions("ipam.add_ipaddress", "dcim.change_interface")
|
|
2275
|
+
|
|
2276
|
+
url = reverse("dcim:device_interfaces", kwargs={"pk": device.pk})
|
|
2277
|
+
response = self.client.get(url)
|
|
2278
|
+
self.assertHttpStatus(response, 200)
|
|
2279
|
+
response_body = response.content.decode(response.charset)
|
|
2280
|
+
# Count the number of occurrences of "Add IP address" in the response_body
|
|
2281
|
+
count = response_body.count("Add IP address")
|
|
2282
|
+
# Assert that "Add IP address" appears for each of the three interfaces
|
|
2283
|
+
self.assertEqual(count, 3)
|
|
2284
|
+
|
|
2285
|
+
def test_device_interface_assign_ipaddress(self):
|
|
2286
|
+
device = Device.objects.first()
|
|
2287
|
+
self.add_permissions(
|
|
2288
|
+
"ipam.add_ipaddress",
|
|
2289
|
+
"extras.view_status",
|
|
2290
|
+
"ipam.view_namespace",
|
|
2291
|
+
"dcim.view_device",
|
|
2292
|
+
"dcim.view_interface",
|
|
2293
|
+
)
|
|
2294
|
+
device_list_url = reverse("dcim:device_interfaces", args=(device.pk,))
|
|
2295
|
+
namespace = Namespace.objects.first()
|
|
2296
|
+
ipaddresses = [str(ipadress) for ipadress in IPAddress.objects.values_list("pk", flat=True)[:3]]
|
|
2297
|
+
add_new_ip_form_data = {
|
|
2298
|
+
"namespace": namespace.pk,
|
|
2299
|
+
"address": "1.1.1.7/24",
|
|
2300
|
+
"tenant": None,
|
|
2301
|
+
"status": Status.objects.get_for_model(IPAddress).first().pk,
|
|
2302
|
+
"type": IPAddressTypeChoices.TYPE_DHCP,
|
|
2303
|
+
"role": None,
|
|
2304
|
+
"nat_inside": None,
|
|
2305
|
+
"dns_name": None,
|
|
2306
|
+
"description": None,
|
|
2307
|
+
"tags": [],
|
|
2308
|
+
"interface": self.interfaces[0].id,
|
|
2309
|
+
}
|
|
2310
|
+
add_new_ip_request = {
|
|
2311
|
+
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={device_list_url}",
|
|
2312
|
+
"data": post_data(add_new_ip_form_data),
|
|
2313
|
+
}
|
|
2314
|
+
assign_ip_form_data = {"pk": ipaddresses}
|
|
2315
|
+
assign_ip_request = {
|
|
2316
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
2317
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
2318
|
+
"data": post_data(assign_ip_form_data),
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
with self.subTest("Assert Cannnot assign IPAddress('Add New') without permission"):
|
|
2322
|
+
# Assert Add new IPAddress
|
|
2323
|
+
response = self.client.post(**add_new_ip_request, follow=True)
|
|
2324
|
+
response_body = response.content.decode(response.charset)
|
|
2325
|
+
self.assertHttpStatus(response, 200)
|
|
2326
|
+
self.interfaces[0].refresh_from_db()
|
|
2327
|
+
self.assertEqual(self.interfaces[0].ip_addresses.all().count(), 0)
|
|
2328
|
+
self.assertIn(
|
|
2329
|
+
f"Interface with id "{self.interfaces[0].pk}" not found",
|
|
2330
|
+
response_body,
|
|
2331
|
+
)
|
|
2332
|
+
|
|
2333
|
+
with self.subTest("Assert Cannnot assign IPAddress(Exsisting IP) without permission"):
|
|
2334
|
+
# Assert Assign Exsisting IPAddress
|
|
2335
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
2336
|
+
response_body = response.content.decode(response.charset)
|
|
2337
|
+
self.assertHttpStatus(response, 200)
|
|
2338
|
+
self.interfaces[1].refresh_from_db()
|
|
2339
|
+
self.assertEqual(self.interfaces[1].ip_addresses.all().count(), 0)
|
|
2340
|
+
self.assertIn(
|
|
2341
|
+
f"Interface with id "{self.interfaces[1].pk}" not found",
|
|
2342
|
+
response_body,
|
|
2343
|
+
)
|
|
2344
|
+
|
|
2345
|
+
self.add_permissions("dcim.change_interface", "ipam.view_ipaddress")
|
|
2346
|
+
|
|
2347
|
+
with self.subTest("Assert Create and Assign IPAddress"):
|
|
2348
|
+
self.assertHttpStatus(self.client.post(**add_new_ip_request), 302)
|
|
2349
|
+
self.interfaces[0].refresh_from_db()
|
|
2350
|
+
self.assertEqual(
|
|
2351
|
+
str(self.interfaces[0].ip_addresses.all().first().address),
|
|
2352
|
+
add_new_ip_form_data["address"],
|
|
2353
|
+
)
|
|
2354
|
+
|
|
2355
|
+
with self.subTest("Assert Assign IPAddress"):
|
|
2356
|
+
response = self.client.post(**assign_ip_request)
|
|
2357
|
+
self.assertHttpStatus(response, 302)
|
|
2358
|
+
self.interfaces[1].refresh_from_db()
|
|
2359
|
+
self.assertEqual(self.interfaces[1].ip_addresses.count(), 3)
|
|
2360
|
+
interface_ips = [str(ip) for ip in self.interfaces[1].ip_addresses.values_list("pk", flat=True)]
|
|
2361
|
+
self.assertEqual(
|
|
2362
|
+
sorted(ipaddresses),
|
|
2363
|
+
sorted(interface_ips),
|
|
2364
|
+
)
|
|
2365
|
+
|
|
2366
|
+
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
2367
|
+
assign_ip_form_data["pk"] = []
|
|
2368
|
+
assign_ip_request = {
|
|
2369
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
2370
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
2371
|
+
"data": post_data(assign_ip_form_data),
|
|
2372
|
+
}
|
|
2373
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
2374
|
+
self.assertHttpStatus(response, 200)
|
|
2375
|
+
self.assertIn(
|
|
2376
|
+
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
2377
|
+
)
|
|
2378
|
+
|
|
2379
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2380
|
+
def test_device_rearports(self):
|
|
2381
|
+
device = Device.objects.first()
|
|
2382
|
+
|
|
2383
|
+
RearPort.objects.create(device=device, name="Rear Port 1")
|
|
2384
|
+
RearPort.objects.create(device=device, name="Rear Port 2")
|
|
2385
|
+
RearPort.objects.create(device=device, name="Rear Port 3")
|
|
2386
|
+
|
|
2387
|
+
url = reverse("dcim:device_rearports", kwargs={"pk": device.pk})
|
|
2388
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2389
|
+
|
|
2390
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2391
|
+
def test_device_frontports(self):
|
|
2392
|
+
device = Device.objects.first()
|
|
2393
|
+
rear_ports = (
|
|
2394
|
+
RearPort.objects.create(device=device, name="Rear Port 1"),
|
|
2395
|
+
RearPort.objects.create(device=device, name="Rear Port 2"),
|
|
2396
|
+
RearPort.objects.create(device=device, name="Rear Port 3"),
|
|
2397
|
+
)
|
|
2398
|
+
|
|
2399
|
+
FrontPort.objects.create(
|
|
2400
|
+
device=device,
|
|
2401
|
+
name="Front Port 1",
|
|
2402
|
+
rear_port=rear_ports[0],
|
|
2403
|
+
rear_port_position=1,
|
|
2404
|
+
)
|
|
2405
|
+
FrontPort.objects.create(
|
|
2406
|
+
device=device,
|
|
2407
|
+
name="Front Port 2",
|
|
2408
|
+
rear_port=rear_ports[1],
|
|
2409
|
+
rear_port_position=1,
|
|
2410
|
+
)
|
|
2411
|
+
FrontPort.objects.create(
|
|
2412
|
+
device=device,
|
|
2413
|
+
name="Front Port 3",
|
|
2414
|
+
rear_port=rear_ports[2],
|
|
2415
|
+
rear_port_position=1,
|
|
2416
|
+
)
|
|
2417
|
+
|
|
2418
|
+
url = reverse("dcim:device_frontports", kwargs={"pk": device.pk})
|
|
2419
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2420
|
+
|
|
2421
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2422
|
+
def test_device_devicebays(self):
|
|
2423
|
+
device = Device.objects.first()
|
|
2424
|
+
|
|
2425
|
+
# Device Bay 1 was already created in setUpTestData()
|
|
2426
|
+
DeviceBay.objects.create(device=device, name="Device Bay 2")
|
|
2427
|
+
DeviceBay.objects.create(device=device, name="Device Bay 3")
|
|
2428
|
+
|
|
2429
|
+
url = reverse("dcim:device_devicebays", kwargs={"pk": device.pk})
|
|
2430
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2431
|
+
|
|
2432
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2433
|
+
def test_device_inventory(self):
|
|
2434
|
+
device = Device.objects.first()
|
|
2435
|
+
|
|
2436
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 1")
|
|
2437
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 2")
|
|
2438
|
+
InventoryItem.objects.create(device=device, name="Inventory Item 3")
|
|
2439
|
+
|
|
2440
|
+
url = reverse("dcim:device_inventory", kwargs={"pk": device.pk})
|
|
2441
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2442
|
+
|
|
2443
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2444
|
+
def test_device_primary_ips(self):
|
|
2445
|
+
"""Test assigning a primary IP to a device."""
|
|
2446
|
+
self.add_permissions("dcim.change_device")
|
|
2447
|
+
|
|
2448
|
+
# Create an interface and assign an IP to it.
|
|
2449
|
+
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
2450
|
+
interface = device.interfaces.first()
|
|
2451
|
+
namespace = Namespace.objects.first()
|
|
2452
|
+
Prefix.objects.create(prefix="1.2.3.0/24", namespace=namespace, status=self.prefix_status)
|
|
2453
|
+
ip_address = IPAddress.objects.create(address="1.2.3.4/32", namespace=namespace, status=self.ipaddr_status)
|
|
2454
|
+
interface.ip_addresses.add(ip_address)
|
|
2455
|
+
|
|
2456
|
+
# Dupe the form data and populated primary_ip4 w/ ip_address
|
|
2457
|
+
form_data = self.form_data.copy()
|
|
2458
|
+
form_data["primary_ip4"] = ip_address.pk
|
|
2459
|
+
# Assert that update succeeds.
|
|
2460
|
+
request = {
|
|
2461
|
+
"path": self._get_url("edit", device),
|
|
2462
|
+
"data": post_data(form_data),
|
|
2463
|
+
}
|
|
2464
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
2465
|
+
self.assertInstanceEqual(self._get_queryset().order_by("last_updated").last(), form_data)
|
|
2466
|
+
|
|
2467
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2468
|
+
def test_local_config_context_schema_validation_pass(self):
|
|
2469
|
+
"""
|
|
2470
|
+
Given a config context schema
|
|
2471
|
+
And a device with local context that conforms to that schema
|
|
2472
|
+
Assert that the local context passes schema validation via full_clean()
|
|
2473
|
+
"""
|
|
2474
|
+
schema = ConfigContextSchema.objects.create(
|
|
2475
|
+
name="Schema 1",
|
|
2476
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
2477
|
+
)
|
|
2478
|
+
self.add_permissions("dcim.add_device")
|
|
2479
|
+
|
|
2480
|
+
form_data = self.form_data.copy()
|
|
2481
|
+
form_data["local_config_context_schema"] = schema.pk
|
|
2482
|
+
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
2483
|
+
|
|
2484
|
+
# Try POST with model-level permission
|
|
2485
|
+
request = {
|
|
2486
|
+
"path": self._get_url("add"),
|
|
2487
|
+
"data": post_data(form_data),
|
|
2488
|
+
}
|
|
2489
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
2490
|
+
self.assertEqual(
|
|
2491
|
+
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
2492
|
+
schema.pk,
|
|
2493
|
+
)
|
|
2494
|
+
|
|
2495
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2496
|
+
def test_local_config_context_schema_validation_fails(self):
|
|
2497
|
+
"""
|
|
2498
|
+
Given a config context schema
|
|
2499
|
+
And a device with local context that *does not* conform to that schema
|
|
2500
|
+
Assert that the local context fails schema validation via full_clean()
|
|
2501
|
+
"""
|
|
2502
|
+
schema = ConfigContextSchema.objects.create(
|
|
2503
|
+
name="Schema 1",
|
|
2504
|
+
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
2505
|
+
)
|
|
2506
|
+
self.add_permissions("dcim.add_device")
|
|
2507
|
+
|
|
2508
|
+
form_data = self.form_data.copy()
|
|
2509
|
+
form_data["local_config_context_schema"] = schema.pk
|
|
2510
|
+
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
2511
|
+
|
|
2512
|
+
# Try POST with model-level permission
|
|
2513
|
+
request = {
|
|
2514
|
+
"path": self._get_url("add"),
|
|
2515
|
+
"data": post_data(form_data),
|
|
2516
|
+
}
|
|
2517
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
2518
|
+
self.assertEqual(self._get_queryset().filter(name="Device X").count(), 0)
|
|
2519
|
+
|
|
2520
|
+
|
|
2521
|
+
class ModuleTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
2522
|
+
model = Module
|
|
2523
|
+
|
|
2524
|
+
@classmethod
|
|
2525
|
+
def setUpTestData(cls):
|
|
2526
|
+
Module.objects.all().delete()
|
|
2527
|
+
locations = Location.objects.filter(location_type=LocationType.objects.get(name="Campus"))[:2]
|
|
2528
|
+
manufacturer = Manufacturer.objects.first()
|
|
2529
|
+
|
|
2530
|
+
moduletypes = (
|
|
2531
|
+
ModuleType.objects.create(model="Module Type 1", manufacturer=manufacturer),
|
|
2532
|
+
ModuleType.objects.create(model="Module Type 2", manufacturer=manufacturer),
|
|
2533
|
+
)
|
|
2534
|
+
|
|
2535
|
+
moduleroles = Role.objects.get_for_model(Module)[:2]
|
|
2536
|
+
|
|
2537
|
+
statuses = Status.objects.get_for_model(Module)
|
|
2538
|
+
status_active = statuses[0]
|
|
2539
|
+
|
|
2540
|
+
cls.custom_fields = (
|
|
2541
|
+
CustomField.objects.create(
|
|
2542
|
+
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
2543
|
+
label="Crash Counter",
|
|
2544
|
+
default=0,
|
|
2545
|
+
),
|
|
2546
|
+
)
|
|
2547
|
+
cls.custom_fields[0].content_types.set([ContentType.objects.get_for_model(Module)])
|
|
2548
|
+
|
|
2549
|
+
modules = (
|
|
2550
|
+
Module.objects.create(
|
|
2551
|
+
location=locations[0],
|
|
2552
|
+
module_type=moduletypes[0],
|
|
2553
|
+
role=moduleroles[0],
|
|
2554
|
+
status=status_active,
|
|
2555
|
+
_custom_field_data={"crash_counter": 5},
|
|
2556
|
+
),
|
|
2557
|
+
Module.objects.create(
|
|
2558
|
+
location=locations[0],
|
|
2559
|
+
module_type=moduletypes[0],
|
|
2560
|
+
role=moduleroles[0],
|
|
2561
|
+
status=status_active,
|
|
2562
|
+
_custom_field_data={"crash_counter": 10},
|
|
2563
|
+
),
|
|
2564
|
+
Module.objects.create(
|
|
2565
|
+
location=locations[0],
|
|
2566
|
+
module_type=moduletypes[0],
|
|
2567
|
+
role=moduleroles[0],
|
|
2568
|
+
status=status_active,
|
|
2569
|
+
_custom_field_data={"crash_counter": 15},
|
|
2570
|
+
),
|
|
2571
|
+
)
|
|
2572
|
+
|
|
2573
|
+
cls.relationships = (
|
|
2574
|
+
Relationship(
|
|
2575
|
+
label="BGP Router-ID",
|
|
2576
|
+
key="router_id",
|
|
2577
|
+
type=RelationshipTypeChoices.TYPE_ONE_TO_ONE,
|
|
2578
|
+
source_type=ContentType.objects.get_for_model(Module),
|
|
2579
|
+
source_label="BGP Router ID",
|
|
2580
|
+
destination_type=ContentType.objects.get_for_model(IPAddress),
|
|
2581
|
+
destination_label="Module using this as BGP router-ID",
|
|
2582
|
+
),
|
|
2583
|
+
)
|
|
2584
|
+
for relationship in cls.relationships:
|
|
2585
|
+
relationship.validated_save()
|
|
2586
|
+
|
|
2587
|
+
cls.ipaddr_status = Status.objects.get_for_model(IPAddress).first()
|
|
2588
|
+
cls.prefix_status = Status.objects.get_for_model(Prefix).first()
|
|
2589
|
+
namespace = Namespace.objects.first()
|
|
2590
|
+
Prefix.objects.create(prefix="1.1.1.1/24", namespace=namespace, status=cls.prefix_status)
|
|
2591
|
+
Prefix.objects.create(prefix="2.2.2.2/24", namespace=namespace, status=cls.prefix_status)
|
|
2592
|
+
Prefix.objects.create(prefix="3.3.3.3/24", namespace=namespace, status=cls.prefix_status)
|
|
2593
|
+
ipaddresses = (
|
|
2594
|
+
IPAddress.objects.create(address="1.1.1.1/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2595
|
+
IPAddress.objects.create(address="2.2.2.2/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2596
|
+
IPAddress.objects.create(address="3.3.3.3/32", namespace=namespace, status=cls.ipaddr_status),
|
|
2597
|
+
)
|
|
2598
|
+
|
|
2599
|
+
intf_status = Status.objects.get_for_model(Interface).first()
|
|
2600
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
2601
|
+
cls.interfaces = (
|
|
2602
|
+
Interface.objects.create(module=modules[0], name="Interface A1", status=intf_status, role=intf_role),
|
|
2603
|
+
Interface.objects.create(module=modules[0], name="Interface A2", status=intf_status),
|
|
2604
|
+
Interface.objects.create(module=modules[0], name="Interface A3", status=intf_status, role=intf_role),
|
|
2605
|
+
)
|
|
2606
|
+
|
|
2607
|
+
for module, ipaddress in zip(modules, ipaddresses):
|
|
2608
|
+
RelationshipAssociation(
|
|
2609
|
+
relationship=cls.relationships[0], source=module, destination=ipaddress
|
|
2610
|
+
).validated_save()
|
|
2611
|
+
|
|
2612
|
+
cls.form_data = {
|
|
2613
|
+
"module_type": moduletypes[1].pk,
|
|
2614
|
+
"role": moduleroles[1].pk,
|
|
2615
|
+
"tenant": None,
|
|
2616
|
+
"serial": "VMWARE-XX XX XX XX XX XX XX XX-XX XX XX XX XX XX XX XX",
|
|
2617
|
+
"asset_tag": generate_random_device_asset_tag_of_specified_size(100),
|
|
2618
|
+
"location": locations[1].pk,
|
|
2619
|
+
"status": statuses[1].pk,
|
|
2620
|
+
"tags": [t.pk for t in Tag.objects.get_for_model(Module)],
|
|
2621
|
+
"cf_crash_counter": -1,
|
|
2622
|
+
"cr_router-id": None,
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
cls.bulk_edit_data = {
|
|
2626
|
+
"role": moduleroles[1].pk,
|
|
2627
|
+
"tenant": None,
|
|
2628
|
+
"status": statuses[2].pk,
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2632
|
+
def test_module_consoleports(self):
|
|
2633
|
+
module = Module.objects.first()
|
|
2634
|
+
|
|
2635
|
+
ConsolePort.objects.create(module=module, name="Console Port 1")
|
|
2636
|
+
ConsolePort.objects.create(module=module, name="Console Port 2")
|
|
2637
|
+
ConsolePort.objects.create(module=module, name="Console Port 3")
|
|
1660
2638
|
|
|
1661
|
-
url = reverse("dcim:
|
|
2639
|
+
url = reverse("dcim:module_consoleports", kwargs={"pk": module.pk})
|
|
1662
2640
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1663
2641
|
|
|
1664
2642
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1665
|
-
def
|
|
1666
|
-
|
|
2643
|
+
def test_module_consoleserverports(self):
|
|
2644
|
+
module = Module.objects.first()
|
|
1667
2645
|
|
|
1668
|
-
ConsoleServerPort.objects.create(
|
|
1669
|
-
ConsoleServerPort.objects.create(
|
|
1670
|
-
ConsoleServerPort.objects.create(
|
|
2646
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 1")
|
|
2647
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 2")
|
|
2648
|
+
ConsoleServerPort.objects.create(module=module, name="Console Server Port 3")
|
|
1671
2649
|
|
|
1672
|
-
url = reverse("dcim:
|
|
2650
|
+
url = reverse("dcim:module_consoleserverports", kwargs={"pk": module.pk})
|
|
1673
2651
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1674
2652
|
|
|
1675
2653
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1676
|
-
def
|
|
1677
|
-
|
|
2654
|
+
def test_module_powerports(self):
|
|
2655
|
+
module = Module.objects.first()
|
|
1678
2656
|
|
|
1679
|
-
PowerPort.objects.create(
|
|
1680
|
-
PowerPort.objects.create(
|
|
1681
|
-
PowerPort.objects.create(
|
|
2657
|
+
PowerPort.objects.create(module=module, name="Power Port 1")
|
|
2658
|
+
PowerPort.objects.create(module=module, name="Power Port 2")
|
|
2659
|
+
PowerPort.objects.create(module=module, name="Power Port 3")
|
|
1682
2660
|
|
|
1683
|
-
url = reverse("dcim:
|
|
2661
|
+
url = reverse("dcim:module_powerports", kwargs={"pk": module.pk})
|
|
1684
2662
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1685
2663
|
|
|
1686
2664
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1687
|
-
def
|
|
1688
|
-
|
|
2665
|
+
def test_module_poweroutlets(self):
|
|
2666
|
+
module = Module.objects.first()
|
|
1689
2667
|
|
|
1690
|
-
PowerOutlet.objects.create(
|
|
1691
|
-
PowerOutlet.objects.create(
|
|
1692
|
-
PowerOutlet.objects.create(
|
|
2668
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 1")
|
|
2669
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 2")
|
|
2670
|
+
PowerOutlet.objects.create(module=module, name="Power Outlet 3")
|
|
1693
2671
|
|
|
1694
|
-
url = reverse("dcim:
|
|
2672
|
+
url = reverse("dcim:module_poweroutlets", kwargs={"pk": module.pk})
|
|
1695
2673
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1696
2674
|
|
|
1697
2675
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1698
|
-
def
|
|
1699
|
-
|
|
2676
|
+
def test_module_interfaces(self):
|
|
2677
|
+
module = Module.objects.filter(interfaces__isnull=False).first()
|
|
1700
2678
|
self.add_permissions("ipam.add_ipaddress", "dcim.change_interface")
|
|
1701
2679
|
|
|
1702
|
-
url = reverse("dcim:
|
|
2680
|
+
url = reverse("dcim:module_interfaces", kwargs={"pk": module.pk})
|
|
1703
2681
|
response = self.client.get(url)
|
|
1704
2682
|
self.assertHttpStatus(response, 200)
|
|
1705
2683
|
response_body = response.content.decode(response.charset)
|
|
@@ -1708,16 +2686,16 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1708
2686
|
# Assert that "Add IP address" appears for each of the three interfaces
|
|
1709
2687
|
self.assertEqual(count, 3)
|
|
1710
2688
|
|
|
1711
|
-
def
|
|
1712
|
-
|
|
2689
|
+
def test_module_interface_assign_ipaddress(self):
|
|
2690
|
+
module = Module.objects.first()
|
|
1713
2691
|
self.add_permissions(
|
|
1714
2692
|
"ipam.add_ipaddress",
|
|
1715
2693
|
"extras.view_status",
|
|
1716
2694
|
"ipam.view_namespace",
|
|
1717
|
-
"dcim.
|
|
2695
|
+
"dcim.view_module",
|
|
1718
2696
|
"dcim.view_interface",
|
|
1719
2697
|
)
|
|
1720
|
-
|
|
2698
|
+
module_list_url = reverse("dcim:module_interfaces", args=(module.pk,))
|
|
1721
2699
|
namespace = Namespace.objects.first()
|
|
1722
2700
|
ipaddresses = [str(ipadress) for ipadress in IPAddress.objects.values_list("pk", flat=True)[:3]]
|
|
1723
2701
|
add_new_ip_form_data = {
|
|
@@ -1734,13 +2712,13 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1734
2712
|
"interface": self.interfaces[0].id,
|
|
1735
2713
|
}
|
|
1736
2714
|
add_new_ip_request = {
|
|
1737
|
-
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={
|
|
2715
|
+
"path": reverse("ipam:ipaddress_add") + f"?interface={self.interfaces[0].id}&return_url={module_list_url}",
|
|
1738
2716
|
"data": post_data(add_new_ip_form_data),
|
|
1739
2717
|
}
|
|
1740
2718
|
assign_ip_form_data = {"pk": ipaddresses}
|
|
1741
2719
|
assign_ip_request = {
|
|
1742
2720
|
"path": reverse("ipam:ipaddress_assign")
|
|
1743
|
-
+ f"?interface={self.interfaces[1].id}&return_url={
|
|
2721
|
+
+ f"?interface={self.interfaces[1].id}&return_url={module_list_url}",
|
|
1744
2722
|
"data": post_data(assign_ip_form_data),
|
|
1745
2723
|
}
|
|
1746
2724
|
|
|
@@ -1790,146 +2768,58 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1790
2768
|
)
|
|
1791
2769
|
|
|
1792
2770
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1793
|
-
def
|
|
1794
|
-
|
|
2771
|
+
def test_module_rearports(self):
|
|
2772
|
+
module = Module.objects.first()
|
|
1795
2773
|
|
|
1796
|
-
RearPort.objects.create(
|
|
1797
|
-
RearPort.objects.create(
|
|
1798
|
-
RearPort.objects.create(
|
|
2774
|
+
RearPort.objects.create(module=module, name="Rear Port 1")
|
|
2775
|
+
RearPort.objects.create(module=module, name="Rear Port 2")
|
|
2776
|
+
RearPort.objects.create(module=module, name="Rear Port 3")
|
|
1799
2777
|
|
|
1800
|
-
url = reverse("dcim:
|
|
2778
|
+
url = reverse("dcim:module_rearports", kwargs={"pk": module.pk})
|
|
1801
2779
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1802
2780
|
|
|
1803
2781
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1804
|
-
def
|
|
1805
|
-
|
|
2782
|
+
def test_module_frontports(self):
|
|
2783
|
+
module = Module.objects.first()
|
|
1806
2784
|
rear_ports = (
|
|
1807
|
-
RearPort.objects.create(
|
|
1808
|
-
RearPort.objects.create(
|
|
1809
|
-
RearPort.objects.create(
|
|
2785
|
+
RearPort.objects.create(module=module, name="Rear Port 1"),
|
|
2786
|
+
RearPort.objects.create(module=module, name="Rear Port 2"),
|
|
2787
|
+
RearPort.objects.create(module=module, name="Rear Port 3"),
|
|
1810
2788
|
)
|
|
1811
2789
|
|
|
1812
2790
|
FrontPort.objects.create(
|
|
1813
|
-
|
|
2791
|
+
module=module,
|
|
1814
2792
|
name="Front Port 1",
|
|
1815
2793
|
rear_port=rear_ports[0],
|
|
1816
2794
|
rear_port_position=1,
|
|
1817
2795
|
)
|
|
1818
2796
|
FrontPort.objects.create(
|
|
1819
|
-
|
|
2797
|
+
module=module,
|
|
1820
2798
|
name="Front Port 2",
|
|
1821
2799
|
rear_port=rear_ports[1],
|
|
1822
2800
|
rear_port_position=1,
|
|
1823
2801
|
)
|
|
1824
2802
|
FrontPort.objects.create(
|
|
1825
|
-
|
|
2803
|
+
module=module,
|
|
1826
2804
|
name="Front Port 3",
|
|
1827
2805
|
rear_port=rear_ports[2],
|
|
1828
2806
|
rear_port_position=1,
|
|
1829
2807
|
)
|
|
1830
2808
|
|
|
1831
|
-
url = reverse("dcim:
|
|
1832
|
-
self.assertHttpStatus(self.client.get(url), 200)
|
|
1833
|
-
|
|
1834
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1835
|
-
def test_device_devicebays(self):
|
|
1836
|
-
device = Device.objects.first()
|
|
1837
|
-
|
|
1838
|
-
# Device Bay 1 was already created in setUpTestData()
|
|
1839
|
-
DeviceBay.objects.create(device=device, name="Device Bay 2")
|
|
1840
|
-
DeviceBay.objects.create(device=device, name="Device Bay 3")
|
|
1841
|
-
|
|
1842
|
-
url = reverse("dcim:device_devicebays", kwargs={"pk": device.pk})
|
|
2809
|
+
url = reverse("dcim:module_frontports", kwargs={"pk": module.pk})
|
|
1843
2810
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1844
2811
|
|
|
1845
2812
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1846
|
-
def
|
|
1847
|
-
|
|
2813
|
+
def test_module_modulebays(self):
|
|
2814
|
+
module = Module.objects.first()
|
|
1848
2815
|
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
2816
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 1")
|
|
2817
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 2")
|
|
2818
|
+
ModuleBay.objects.create(parent_module=module, name="Test View Module Bay 3")
|
|
1852
2819
|
|
|
1853
|
-
url = reverse("dcim:
|
|
2820
|
+
url = reverse("dcim:module_modulebays", kwargs={"pk": module.pk})
|
|
1854
2821
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1855
2822
|
|
|
1856
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1857
|
-
def test_device_primary_ips(self):
|
|
1858
|
-
"""Test assigning a primary IP to a device."""
|
|
1859
|
-
self.add_permissions("dcim.change_device")
|
|
1860
|
-
|
|
1861
|
-
# Create an interface and assign an IP to it.
|
|
1862
|
-
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
1863
|
-
interface = device.interfaces.first()
|
|
1864
|
-
namespace = Namespace.objects.first()
|
|
1865
|
-
Prefix.objects.create(prefix="1.2.3.0/24", namespace=namespace, status=self.prefix_status)
|
|
1866
|
-
ip_address = IPAddress.objects.create(address="1.2.3.4/32", namespace=namespace, status=self.ipaddr_status)
|
|
1867
|
-
interface.ip_addresses.add(ip_address)
|
|
1868
|
-
|
|
1869
|
-
# Dupe the form data and populated primary_ip4 w/ ip_address
|
|
1870
|
-
form_data = self.form_data.copy()
|
|
1871
|
-
form_data["primary_ip4"] = ip_address.pk
|
|
1872
|
-
# Assert that update succeeds.
|
|
1873
|
-
request = {
|
|
1874
|
-
"path": self._get_url("edit", device),
|
|
1875
|
-
"data": post_data(form_data),
|
|
1876
|
-
}
|
|
1877
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1878
|
-
self.assertInstanceEqual(self._get_queryset().order_by("last_updated").last(), form_data)
|
|
1879
|
-
|
|
1880
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1881
|
-
def test_local_config_context_schema_validation_pass(self):
|
|
1882
|
-
"""
|
|
1883
|
-
Given a config context schema
|
|
1884
|
-
And a device with local context that conforms to that schema
|
|
1885
|
-
Assert that the local context passes schema validation via full_clean()
|
|
1886
|
-
"""
|
|
1887
|
-
schema = ConfigContextSchema.objects.create(
|
|
1888
|
-
name="Schema 1",
|
|
1889
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
1890
|
-
)
|
|
1891
|
-
self.add_permissions("dcim.add_device")
|
|
1892
|
-
|
|
1893
|
-
form_data = self.form_data.copy()
|
|
1894
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1895
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1896
|
-
|
|
1897
|
-
# Try POST with model-level permission
|
|
1898
|
-
request = {
|
|
1899
|
-
"path": self._get_url("add"),
|
|
1900
|
-
"data": post_data(form_data),
|
|
1901
|
-
}
|
|
1902
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1903
|
-
self.assertEqual(
|
|
1904
|
-
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
1905
|
-
schema.pk,
|
|
1906
|
-
)
|
|
1907
|
-
|
|
1908
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1909
|
-
def test_local_config_context_schema_validation_fails(self):
|
|
1910
|
-
"""
|
|
1911
|
-
Given a config context schema
|
|
1912
|
-
And a device with local context that *does not* conform to that schema
|
|
1913
|
-
Assert that the local context fails schema validation via full_clean()
|
|
1914
|
-
"""
|
|
1915
|
-
schema = ConfigContextSchema.objects.create(
|
|
1916
|
-
name="Schema 1",
|
|
1917
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
1918
|
-
)
|
|
1919
|
-
self.add_permissions("dcim.add_device")
|
|
1920
|
-
|
|
1921
|
-
form_data = self.form_data.copy()
|
|
1922
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1923
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1924
|
-
|
|
1925
|
-
# Try POST with model-level permission
|
|
1926
|
-
request = {
|
|
1927
|
-
"path": self._get_url("add"),
|
|
1928
|
-
"data": post_data(form_data),
|
|
1929
|
-
}
|
|
1930
|
-
self.assertHttpStatus(self.client.post(**request), 200)
|
|
1931
|
-
self.assertEqual(self._get_queryset().filter(name="Device X").count(), 0)
|
|
1932
|
-
|
|
1933
2823
|
|
|
1934
2824
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1935
2825
|
model = ConsolePort
|
|
@@ -1970,6 +2860,15 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
1970
2860
|
"description": "New description",
|
|
1971
2861
|
}
|
|
1972
2862
|
|
|
2863
|
+
test_instance = cls.model.objects.first()
|
|
2864
|
+
cls.update_data = {
|
|
2865
|
+
"name": test_instance.name,
|
|
2866
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2867
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2868
|
+
"label": "new test label",
|
|
2869
|
+
"description": "new test description",
|
|
2870
|
+
}
|
|
2871
|
+
|
|
1973
2872
|
|
|
1974
2873
|
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1975
2874
|
model = ConsoleServerPort
|
|
@@ -2009,6 +2908,15 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2009
2908
|
"description": "New description",
|
|
2010
2909
|
}
|
|
2011
2910
|
|
|
2911
|
+
test_instance = cls.model.objects.first()
|
|
2912
|
+
cls.update_data = {
|
|
2913
|
+
"name": test_instance.name,
|
|
2914
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2915
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2916
|
+
"label": "new test label",
|
|
2917
|
+
"description": "new test description",
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2012
2920
|
|
|
2013
2921
|
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2014
2922
|
model = PowerPort
|
|
@@ -2053,12 +2961,22 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2053
2961
|
"description": "New description",
|
|
2054
2962
|
}
|
|
2055
2963
|
|
|
2964
|
+
test_instance = cls.model.objects.first()
|
|
2965
|
+
cls.update_data = {
|
|
2966
|
+
"name": test_instance.name,
|
|
2967
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
2968
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
2969
|
+
"label": "new test label",
|
|
2970
|
+
"description": "new test description",
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2056
2973
|
|
|
2057
2974
|
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2058
2975
|
model = PowerOutlet
|
|
2059
2976
|
|
|
2060
2977
|
@classmethod
|
|
2061
2978
|
def setUpTestData(cls):
|
|
2979
|
+
PowerOutlet.objects.all().delete()
|
|
2062
2980
|
device = create_test_device("Device 1")
|
|
2063
2981
|
|
|
2064
2982
|
powerports = (
|
|
@@ -2111,12 +3029,23 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2111
3029
|
"description": "New description",
|
|
2112
3030
|
}
|
|
2113
3031
|
|
|
3032
|
+
test_instance = cls.model.objects.first()
|
|
3033
|
+
cls.update_data = {
|
|
3034
|
+
"name": test_instance.name,
|
|
3035
|
+
"device_type": getattr(getattr(test_instance, "device_type", None), "pk", None),
|
|
3036
|
+
"module_type": getattr(getattr(test_instance, "module_type", None), "pk", None),
|
|
3037
|
+
"power_port": getattr(test_instance.power_port, "pk", None), # power_port must match parent device/module
|
|
3038
|
+
"label": "new test label",
|
|
3039
|
+
"description": "new test description",
|
|
3040
|
+
}
|
|
3041
|
+
|
|
2114
3042
|
|
|
2115
3043
|
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2116
3044
|
model = Interface
|
|
2117
3045
|
|
|
2118
3046
|
@classmethod
|
|
2119
3047
|
def setUpTestData(cls):
|
|
3048
|
+
Interface.objects.all().delete()
|
|
2120
3049
|
device = create_test_device("Device 1")
|
|
2121
3050
|
vrfs = list(VRF.objects.all()[:3])
|
|
2122
3051
|
for vrf in vrfs:
|
|
@@ -2124,22 +3053,24 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2124
3053
|
|
|
2125
3054
|
statuses = Status.objects.get_for_model(Interface)
|
|
2126
3055
|
status_active = statuses[0]
|
|
2127
|
-
|
|
3056
|
+
role = Role.objects.get_for_model(Interface).first()
|
|
2128
3057
|
interfaces = (
|
|
2129
|
-
Interface.objects.create(device=device, name="Interface
|
|
2130
|
-
Interface.objects.create(device=device, name="Interface
|
|
2131
|
-
Interface.objects.create(device=device, name="Interface
|
|
3058
|
+
Interface.objects.create(device=device, name="Interface A1", status=status_active, role=role),
|
|
3059
|
+
Interface.objects.create(device=device, name="Interface A2", status=status_active),
|
|
3060
|
+
Interface.objects.create(device=device, name="Interface A3", status=status_active, role=role),
|
|
2132
3061
|
Interface.objects.create(
|
|
2133
3062
|
device=device,
|
|
2134
3063
|
name="LAG",
|
|
2135
3064
|
status=status_active,
|
|
2136
3065
|
type=InterfaceTypeChoices.TYPE_LAG,
|
|
3066
|
+
role=role,
|
|
2137
3067
|
),
|
|
2138
3068
|
Interface.objects.create(
|
|
2139
3069
|
device=device,
|
|
2140
3070
|
name="BRIDGE",
|
|
2141
3071
|
status=status_active,
|
|
2142
3072
|
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
3073
|
+
role=role,
|
|
2143
3074
|
),
|
|
2144
3075
|
)
|
|
2145
3076
|
cls.lag_interface = interfaces[3]
|
|
@@ -2186,6 +3117,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2186
3117
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2187
3118
|
"enabled": False,
|
|
2188
3119
|
"status": status_active.pk,
|
|
3120
|
+
"role": role.pk,
|
|
2189
3121
|
"lag": interfaces[3].pk,
|
|
2190
3122
|
"mac_address": EUI("01:02:03:04:05:06"),
|
|
2191
3123
|
"mtu": 2000,
|
|
@@ -2214,6 +3146,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2214
3146
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2215
3147
|
"tags": [t.pk for t in Tag.objects.get_for_model(Interface)],
|
|
2216
3148
|
"status": status_active.pk,
|
|
3149
|
+
"role": role.pk,
|
|
2217
3150
|
"vrf": vrfs[0].pk,
|
|
2218
3151
|
}
|
|
2219
3152
|
|
|
@@ -2222,6 +3155,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2222
3155
|
"name_pattern": "Interface [4-6]",
|
|
2223
3156
|
"label_pattern": "Interface Number [4-6]",
|
|
2224
3157
|
"status": status_active.pk,
|
|
3158
|
+
"role": role.pk,
|
|
2225
3159
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2226
3160
|
"enabled": True,
|
|
2227
3161
|
"mtu": 1500,
|
|
@@ -2244,9 +3178,21 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2244
3178
|
"untagged_vlan": vlans[0].pk,
|
|
2245
3179
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2246
3180
|
"status": status_active.pk,
|
|
3181
|
+
"role": role.pk,
|
|
2247
3182
|
"vrf": vrfs[2].pk,
|
|
2248
3183
|
}
|
|
2249
3184
|
|
|
3185
|
+
test_instance = cls.model.objects.first()
|
|
3186
|
+
cls.update_data = {
|
|
3187
|
+
"name": test_instance.name,
|
|
3188
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3189
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3190
|
+
"status": test_instance.status.pk,
|
|
3191
|
+
"type": test_instance.type,
|
|
3192
|
+
"label": "new test label",
|
|
3193
|
+
"description": "new test description",
|
|
3194
|
+
}
|
|
3195
|
+
|
|
2250
3196
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2251
3197
|
def test_create_virtual_interface_with_parent_lag(self):
|
|
2252
3198
|
"""https://github.com/nautobot/nautobot/issues/4436."""
|
|
@@ -2292,18 +3238,66 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2292
3238
|
cls.device = device
|
|
2293
3239
|
|
|
2294
3240
|
rearports = (
|
|
2295
|
-
RearPort.objects.create(
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
3241
|
+
RearPort.objects.create(
|
|
3242
|
+
device=device,
|
|
3243
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3244
|
+
positions=24,
|
|
3245
|
+
name="Rear Port 1",
|
|
3246
|
+
),
|
|
3247
|
+
RearPort.objects.create(
|
|
3248
|
+
device=device,
|
|
3249
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3250
|
+
positions=24,
|
|
3251
|
+
name="Rear Port 2",
|
|
3252
|
+
),
|
|
3253
|
+
RearPort.objects.create(
|
|
3254
|
+
device=device,
|
|
3255
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3256
|
+
positions=24,
|
|
3257
|
+
name="Rear Port 3",
|
|
3258
|
+
),
|
|
3259
|
+
RearPort.objects.create(
|
|
3260
|
+
device=device,
|
|
3261
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3262
|
+
positions=24,
|
|
3263
|
+
name="Rear Port 4",
|
|
3264
|
+
),
|
|
3265
|
+
RearPort.objects.create(
|
|
3266
|
+
device=device,
|
|
3267
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3268
|
+
positions=24,
|
|
3269
|
+
name="Rear Port 5",
|
|
3270
|
+
),
|
|
3271
|
+
RearPort.objects.create(
|
|
3272
|
+
device=device,
|
|
3273
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3274
|
+
positions=24,
|
|
3275
|
+
name="Rear Port 6",
|
|
3276
|
+
),
|
|
2301
3277
|
)
|
|
2302
3278
|
|
|
2303
3279
|
frontports = (
|
|
2304
|
-
FrontPort.objects.create(
|
|
2305
|
-
|
|
2306
|
-
|
|
3280
|
+
FrontPort.objects.create(
|
|
3281
|
+
device=device,
|
|
3282
|
+
name="Front Port 1",
|
|
3283
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3284
|
+
rear_port=rearports[0],
|
|
3285
|
+
rear_port_position=12,
|
|
3286
|
+
),
|
|
3287
|
+
FrontPort.objects.create(
|
|
3288
|
+
device=device,
|
|
3289
|
+
name="Front Port 2",
|
|
3290
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3291
|
+
rear_port=rearports[1],
|
|
3292
|
+
rear_port_position=12,
|
|
3293
|
+
),
|
|
3294
|
+
FrontPort.objects.create(
|
|
3295
|
+
device=device,
|
|
3296
|
+
name="Front Port 3",
|
|
3297
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3298
|
+
rear_port=rearports[2],
|
|
3299
|
+
rear_port_position=12,
|
|
3300
|
+
),
|
|
2307
3301
|
)
|
|
2308
3302
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2309
3303
|
cls.selected_objects = frontports
|
|
@@ -2333,6 +3327,18 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2333
3327
|
"description": "New description",
|
|
2334
3328
|
}
|
|
2335
3329
|
|
|
3330
|
+
test_instance = cls.model.objects.first()
|
|
3331
|
+
cls.update_data = {
|
|
3332
|
+
"name": test_instance.name,
|
|
3333
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3334
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3335
|
+
"rear_port": test_instance.rear_port.pk, # rear_port must match the parent device/module
|
|
3336
|
+
"rear_port_position": test_instance.rear_port_position,
|
|
3337
|
+
"type": test_instance.type,
|
|
3338
|
+
"label": "new test label",
|
|
3339
|
+
"description": "new test description",
|
|
3340
|
+
}
|
|
3341
|
+
|
|
2336
3342
|
@unittest.skip("No DeviceBulkAddFrontPortView exists at present")
|
|
2337
3343
|
def test_bulk_add_component(self):
|
|
2338
3344
|
pass
|
|
@@ -2346,9 +3352,24 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2346
3352
|
device = create_test_device("Device 1")
|
|
2347
3353
|
|
|
2348
3354
|
rearports = (
|
|
2349
|
-
RearPort.objects.create(
|
|
2350
|
-
|
|
2351
|
-
|
|
3355
|
+
RearPort.objects.create(
|
|
3356
|
+
device=device,
|
|
3357
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3358
|
+
positions=24,
|
|
3359
|
+
name="Rear Port 1",
|
|
3360
|
+
),
|
|
3361
|
+
RearPort.objects.create(
|
|
3362
|
+
device=device,
|
|
3363
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3364
|
+
positions=24,
|
|
3365
|
+
name="Rear Port 2",
|
|
3366
|
+
),
|
|
3367
|
+
RearPort.objects.create(
|
|
3368
|
+
device=device,
|
|
3369
|
+
type=PortTypeChoices.TYPE_8P8C,
|
|
3370
|
+
positions=24,
|
|
3371
|
+
name="Rear Port 3",
|
|
3372
|
+
),
|
|
2352
3373
|
)
|
|
2353
3374
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2354
3375
|
cls.selected_objects = rearports
|
|
@@ -2377,6 +3398,17 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2377
3398
|
"description": "New description",
|
|
2378
3399
|
}
|
|
2379
3400
|
|
|
3401
|
+
test_instance = cls.model.objects.first()
|
|
3402
|
+
cls.update_data = {
|
|
3403
|
+
"name": test_instance.name,
|
|
3404
|
+
"device": getattr(getattr(test_instance, "device", None), "pk", None),
|
|
3405
|
+
"module": getattr(getattr(test_instance, "module", None), "pk", None),
|
|
3406
|
+
"positions": test_instance.positions,
|
|
3407
|
+
"type": test_instance.type,
|
|
3408
|
+
"label": "new test label",
|
|
3409
|
+
"description": "new test description",
|
|
3410
|
+
}
|
|
3411
|
+
|
|
2380
3412
|
|
|
2381
3413
|
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2382
3414
|
model = DeviceBay
|
|
@@ -2415,6 +3447,113 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2415
3447
|
"description": "New description",
|
|
2416
3448
|
}
|
|
2417
3449
|
|
|
3450
|
+
test_instance = cls.model.objects.first()
|
|
3451
|
+
cls.update_data = {
|
|
3452
|
+
"name": test_instance.name,
|
|
3453
|
+
"device": test_instance.device.pk,
|
|
3454
|
+
"label": "new test label",
|
|
3455
|
+
"description": "new test description",
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
|
|
3459
|
+
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
3460
|
+
model = ModuleBay
|
|
3461
|
+
|
|
3462
|
+
@classmethod
|
|
3463
|
+
def setUpTestData(cls):
|
|
3464
|
+
device = Device.objects.first()
|
|
3465
|
+
module = Module.objects.first()
|
|
3466
|
+
|
|
3467
|
+
module_bays = (
|
|
3468
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 1"),
|
|
3469
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 2"),
|
|
3470
|
+
ModuleBay.objects.create(parent_device=device, name="Test View Module Bay 3"),
|
|
3471
|
+
)
|
|
3472
|
+
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
3473
|
+
cls.selected_objects = module_bays
|
|
3474
|
+
cls.selected_objects_parent_name = device.name
|
|
3475
|
+
|
|
3476
|
+
cls.form_data = {
|
|
3477
|
+
"parent_device": device.pk,
|
|
3478
|
+
"name": "Test ModuleBay 1",
|
|
3479
|
+
"position": 1,
|
|
3480
|
+
"description": "Test modulebay description",
|
|
3481
|
+
"label": "Test modulebay label",
|
|
3482
|
+
"tags": sorted([t.pk for t in Tag.objects.get_for_model(ModuleBay)]),
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
cls.bulk_create_data = {
|
|
3486
|
+
"parent_module": module.pk,
|
|
3487
|
+
"name_pattern": "Test ModuleBay [0-2]",
|
|
3488
|
+
"position_pattern": "[1-3]",
|
|
3489
|
+
# Test that a label can be applied to each generated module bay
|
|
3490
|
+
"label_pattern": "Slot[1-3]",
|
|
3491
|
+
"description": "Test modulebay description",
|
|
3492
|
+
"tags": sorted([t.pk for t in Tag.objects.get_for_model(ModuleBay)]),
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
cls.bulk_edit_data = {
|
|
3496
|
+
"position": "new position",
|
|
3497
|
+
"description": "New description",
|
|
3498
|
+
"label": "New label",
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
test_instance = cls.model.objects.first()
|
|
3502
|
+
cls.update_data = {
|
|
3503
|
+
"name": test_instance.name,
|
|
3504
|
+
"parent_device": getattr(getattr(test_instance, "parent_device", None), "pk", None),
|
|
3505
|
+
"parent_module": getattr(getattr(test_instance, "parent_module", None), "pk", None),
|
|
3506
|
+
"position": "new test position",
|
|
3507
|
+
"label": "new test label",
|
|
3508
|
+
"description": "new test description",
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
def get_deletable_object_pks(self):
|
|
3512
|
+
# Since Modules and ModuleBays are nestable, we need to delete ModuleBays that don't have any child ModuleBays
|
|
3513
|
+
return ModuleBay.objects.filter(installed_module__isnull=True).values_list("pk", flat=True)[:3]
|
|
3514
|
+
|
|
3515
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3516
|
+
def test_bulk_add_component(self):
|
|
3517
|
+
"""Test bulk-adding this component to modules."""
|
|
3518
|
+
obj_perm = ObjectPermission(name="Test permission", actions=["add"])
|
|
3519
|
+
obj_perm.save()
|
|
3520
|
+
obj_perm.users.add(self.user)
|
|
3521
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
3522
|
+
|
|
3523
|
+
initial_count = self._get_queryset().count()
|
|
3524
|
+
|
|
3525
|
+
data = self.bulk_create_data.copy()
|
|
3526
|
+
|
|
3527
|
+
# Load the module-bulk-add form
|
|
3528
|
+
module_perm = ObjectPermission(name="Module permission", actions=["change"])
|
|
3529
|
+
module_perm.save()
|
|
3530
|
+
module_perm.users.add(self.user)
|
|
3531
|
+
module_perm.object_types.add(ContentType.objects.get_for_model(Module))
|
|
3532
|
+
url = reverse(f"dcim:module_bulk_add_{self.model._meta.model_name}")
|
|
3533
|
+
request = {
|
|
3534
|
+
"path": url,
|
|
3535
|
+
"data": post_data({"pk": data["parent_module"]}),
|
|
3536
|
+
}
|
|
3537
|
+
self.assertHttpStatus(self.client.post(**request), 200)
|
|
3538
|
+
|
|
3539
|
+
# Post to the module-bulk-add form to create records
|
|
3540
|
+
data["pk"] = data.pop("parent_module")
|
|
3541
|
+
data["_create"] = ""
|
|
3542
|
+
request["data"] = post_data(data)
|
|
3543
|
+
self.assertHttpStatus(self.client.post(**request), 302)
|
|
3544
|
+
|
|
3545
|
+
updated_count = self._get_queryset().count()
|
|
3546
|
+
self.assertEqual(updated_count, initial_count + self.bulk_create_count)
|
|
3547
|
+
|
|
3548
|
+
matching_count = 0
|
|
3549
|
+
for instance in self._get_queryset().all():
|
|
3550
|
+
try:
|
|
3551
|
+
self.assertInstanceEqual(instance, self.bulk_create_data)
|
|
3552
|
+
matching_count += 1
|
|
3553
|
+
except AssertionError:
|
|
3554
|
+
pass
|
|
3555
|
+
self.assertEqual(matching_count, self.bulk_create_count)
|
|
3556
|
+
|
|
2418
3557
|
|
|
2419
3558
|
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2420
3559
|
model = InventoryItem
|
|
@@ -2467,6 +3606,14 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2467
3606
|
"software_version": software_versions[2].pk,
|
|
2468
3607
|
}
|
|
2469
3608
|
|
|
3609
|
+
test_instance = cls.model.objects.first()
|
|
3610
|
+
cls.update_data = {
|
|
3611
|
+
"name": test_instance.name,
|
|
3612
|
+
"device": test_instance.device.pk,
|
|
3613
|
+
"label": "new test label",
|
|
3614
|
+
"description": "new test description",
|
|
3615
|
+
}
|
|
3616
|
+
|
|
2470
3617
|
def test_table_with_indentation_is_removed_on_filter_or_sort(self):
|
|
2471
3618
|
self.skipTest("InventoryItem table has no implementation of indentation.")
|
|
2472
3619
|
|
|
@@ -2527,73 +3674,73 @@ class CableTestCase(
|
|
|
2527
3674
|
interfaces = (
|
|
2528
3675
|
Interface.objects.create(
|
|
2529
3676
|
device=devices[0],
|
|
2530
|
-
name="Interface
|
|
3677
|
+
name="Interface A1",
|
|
2531
3678
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2532
3679
|
status=interface_status,
|
|
2533
3680
|
),
|
|
2534
3681
|
Interface.objects.create(
|
|
2535
3682
|
device=devices[0],
|
|
2536
|
-
name="Interface
|
|
3683
|
+
name="Interface A2",
|
|
2537
3684
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2538
3685
|
status=interface_status,
|
|
2539
3686
|
),
|
|
2540
3687
|
Interface.objects.create(
|
|
2541
3688
|
device=devices[0],
|
|
2542
|
-
name="Interface
|
|
3689
|
+
name="Interface A3",
|
|
2543
3690
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2544
3691
|
status=interface_status,
|
|
2545
3692
|
),
|
|
2546
3693
|
Interface.objects.create(
|
|
2547
3694
|
device=devices[1],
|
|
2548
|
-
name="Interface
|
|
3695
|
+
name="Interface A1",
|
|
2549
3696
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2550
3697
|
status=interface_status,
|
|
2551
3698
|
),
|
|
2552
3699
|
Interface.objects.create(
|
|
2553
3700
|
device=devices[1],
|
|
2554
|
-
name="Interface
|
|
3701
|
+
name="Interface A2",
|
|
2555
3702
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2556
3703
|
status=interface_status,
|
|
2557
3704
|
),
|
|
2558
3705
|
Interface.objects.create(
|
|
2559
3706
|
device=devices[1],
|
|
2560
|
-
name="Interface
|
|
3707
|
+
name="Interface A3",
|
|
2561
3708
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2562
3709
|
status=interface_status,
|
|
2563
3710
|
),
|
|
2564
3711
|
Interface.objects.create(
|
|
2565
3712
|
device=devices[2],
|
|
2566
|
-
name="Interface
|
|
3713
|
+
name="Interface A1",
|
|
2567
3714
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2568
3715
|
status=interface_status,
|
|
2569
3716
|
),
|
|
2570
3717
|
Interface.objects.create(
|
|
2571
3718
|
device=devices[2],
|
|
2572
|
-
name="Interface
|
|
3719
|
+
name="Interface A2",
|
|
2573
3720
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2574
3721
|
status=interface_status,
|
|
2575
3722
|
),
|
|
2576
3723
|
Interface.objects.create(
|
|
2577
3724
|
device=devices[2],
|
|
2578
|
-
name="Interface
|
|
3725
|
+
name="Interface A3",
|
|
2579
3726
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2580
3727
|
status=interface_status,
|
|
2581
3728
|
),
|
|
2582
3729
|
Interface.objects.create(
|
|
2583
3730
|
device=devices[3],
|
|
2584
|
-
name="Interface
|
|
3731
|
+
name="Interface A1",
|
|
2585
3732
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2586
3733
|
status=interface_status,
|
|
2587
3734
|
),
|
|
2588
3735
|
Interface.objects.create(
|
|
2589
3736
|
device=devices[3],
|
|
2590
|
-
name="Interface
|
|
3737
|
+
name="Interface A2",
|
|
2591
3738
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2592
3739
|
status=interface_status,
|
|
2593
3740
|
),
|
|
2594
3741
|
Interface.objects.create(
|
|
2595
3742
|
device=devices[3],
|
|
2596
|
-
name="Interface
|
|
3743
|
+
name="Interface A3",
|
|
2597
3744
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2598
3745
|
status=interface_status,
|
|
2599
3746
|
),
|
|
@@ -2745,6 +3892,9 @@ class ConsoleConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2745
3892
|
def _get_base_url(self):
|
|
2746
3893
|
return "dcim:console_connections_{}"
|
|
2747
3894
|
|
|
3895
|
+
def _get_queryset(self):
|
|
3896
|
+
return ConsolePort.objects.filter(cable__isnull=False)
|
|
3897
|
+
|
|
2748
3898
|
def get_list_url(self):
|
|
2749
3899
|
return "/dcim/console-connections/"
|
|
2750
3900
|
|
|
@@ -2797,15 +3947,18 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2797
3947
|
Test the PowerConnectionsListView.
|
|
2798
3948
|
"""
|
|
2799
3949
|
|
|
3950
|
+
def _get_base_url(self):
|
|
3951
|
+
return "dcim:power_connections_{}"
|
|
3952
|
+
|
|
3953
|
+
def _get_queryset(self):
|
|
3954
|
+
return PowerPort.objects.filter(cable__isnull=False)
|
|
3955
|
+
|
|
2800
3956
|
def get_list_url(self):
|
|
2801
3957
|
return "/dcim/power-connections/"
|
|
2802
3958
|
|
|
2803
3959
|
def get_title(self):
|
|
2804
3960
|
return "Power Connections"
|
|
2805
3961
|
|
|
2806
|
-
def _get_base_url(self):
|
|
2807
|
-
return "dcim:power_connections_{}"
|
|
2808
|
-
|
|
2809
3962
|
def get_list_view(self):
|
|
2810
3963
|
return PowerConnectionsListView
|
|
2811
3964
|
|
|
@@ -2862,6 +4015,9 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2862
4015
|
def _get_base_url(self):
|
|
2863
4016
|
return "dcim:interface_connections_{}"
|
|
2864
4017
|
|
|
4018
|
+
def _get_queryset(self):
|
|
4019
|
+
return Interface.objects.filter(cable__isnull=False)
|
|
4020
|
+
|
|
2865
4021
|
def get_list_url(self):
|
|
2866
4022
|
return "/dcim/interface-connections/"
|
|
2867
4023
|
|
|
@@ -2882,22 +4038,25 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2882
4038
|
device_2 = create_test_device("Device 2")
|
|
2883
4039
|
|
|
2884
4040
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
4041
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
2885
4042
|
cls.interfaces = (
|
|
2886
4043
|
Interface.objects.create(
|
|
2887
4044
|
device=device_1,
|
|
2888
|
-
name="Interface
|
|
4045
|
+
name="Interface A1",
|
|
2889
4046
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2890
4047
|
status=interface_status,
|
|
4048
|
+
role=interface_role,
|
|
2891
4049
|
),
|
|
2892
4050
|
Interface.objects.create(
|
|
2893
4051
|
device=device_1,
|
|
2894
|
-
name="Interface
|
|
4052
|
+
name="Interface A2",
|
|
2895
4053
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2896
4054
|
status=interface_status,
|
|
4055
|
+
role=interface_role,
|
|
2897
4056
|
),
|
|
2898
4057
|
Interface.objects.create(
|
|
2899
4058
|
device=device_1,
|
|
2900
|
-
name="Interface
|
|
4059
|
+
name="Interface A3",
|
|
2901
4060
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2902
4061
|
status=interface_status,
|
|
2903
4062
|
),
|
|
@@ -2905,9 +4064,10 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2905
4064
|
|
|
2906
4065
|
cls.device_2_interface = Interface.objects.create(
|
|
2907
4066
|
device=device_2,
|
|
2908
|
-
name="Interface
|
|
4067
|
+
name="Interface A1",
|
|
2909
4068
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2910
4069
|
status=interface_status,
|
|
4070
|
+
role=interface_role,
|
|
2911
4071
|
)
|
|
2912
4072
|
rearport = RearPort.objects.create(device=device_2, type=PortTypeChoices.TYPE_8P8C)
|
|
2913
4073
|
|
|
@@ -3029,6 +4189,28 @@ class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3029
4189
|
"domain": "domain-x",
|
|
3030
4190
|
}
|
|
3031
4191
|
|
|
4192
|
+
def test_device_interfaces_count_correct(self):
|
|
4193
|
+
"""
|
|
4194
|
+
This checks whether the other memebers' interfaces are included in the
|
|
4195
|
+
interfaces tab of the master device and whether the interface count on the tab header is
|
|
4196
|
+
rendered correctly.
|
|
4197
|
+
"""
|
|
4198
|
+
self.user.is_superuser = True
|
|
4199
|
+
self.user.save()
|
|
4200
|
+
interface_status = Status.objects.get_for_model(Interface).first()
|
|
4201
|
+
Interface.objects.create(device=self.devices[0], name="eth0", status=interface_status)
|
|
4202
|
+
Interface.objects.create(device=self.devices[0], name="eth1", status=interface_status)
|
|
4203
|
+
Interface.objects.create(device=self.devices[1], name="device 1 interface 1", status=interface_status)
|
|
4204
|
+
Interface.objects.create(device=self.devices[1], name="device 1 interface 2", status=interface_status)
|
|
4205
|
+
Interface.objects.create(device=self.devices[2], name="device 2 interface 1", status=interface_status)
|
|
4206
|
+
Interface.objects.create(device=self.devices[2], name="device 2 interface 2", status=interface_status)
|
|
4207
|
+
response = self.client.get(reverse("dcim:device_interfaces", kwargs={"pk": self.devices[0].pk}))
|
|
4208
|
+
self.assertIn('Interfaces <span class="badge">6</span>', str(response.content))
|
|
4209
|
+
self.assertIn("device 1 interface 1", str(response.content))
|
|
4210
|
+
self.assertIn("device 1 interface 2", str(response.content))
|
|
4211
|
+
self.assertIn("device 2 interface 1", str(response.content))
|
|
4212
|
+
self.assertIn("device 2 interface 2", str(response.content))
|
|
4213
|
+
|
|
3032
4214
|
def test_device_column_visible(self):
|
|
3033
4215
|
"""
|
|
3034
4216
|
This checks whether the device column on a device's interfaces
|
|
@@ -3313,11 +4495,11 @@ class InterfaceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3313
4495
|
status=status_active,
|
|
3314
4496
|
)
|
|
3315
4497
|
intf_status = Status.objects.get_for_model(Interface).first()
|
|
3316
|
-
|
|
4498
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
3317
4499
|
cls.interfaces = (
|
|
3318
|
-
Interface.objects.create(device=device, name="Interface
|
|
3319
|
-
Interface.objects.create(device=device, name="Interface
|
|
3320
|
-
Interface.objects.create(device=device, name="Interface
|
|
4500
|
+
Interface.objects.create(device=device, name="Interface A1", status=intf_status, role=intf_role),
|
|
4501
|
+
Interface.objects.create(device=device, name="Interface A2", status=intf_status),
|
|
4502
|
+
Interface.objects.create(device=device, name="Interface A3", status=intf_status, role=intf_role),
|
|
3321
4503
|
)
|
|
3322
4504
|
|
|
3323
4505
|
cls.form_data = {
|