nautobot 2.2.9__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 +95 -13
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +11 -3
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/schema.py +26 -4
- nautobot/core/jobs/__init__.py +16 -2
- 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 +2 -17
- nautobot/core/settings.yaml +34 -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 +13 -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_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 +122 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +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 +275 -99
- nautobot/dcim/models/devices.py +468 -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 +229 -43
- 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/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 +1072 -8
- nautobot/dcim/tests/test_views.py +1510 -341
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1106 -116
- 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 +66 -0
- 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 +14 -1
- 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 +6 -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 +95 -100
- nautobot/extras/tables.py +165 -12
- 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/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 +565 -8
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +51 -11
- nautobot/extras/views.py +542 -76
- 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 +11 -8
- 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 +6 -0
- 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 +18 -26
- nautobot/project-static/css/base.css +20 -0
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +892 -88
- nautobot/project-static/docs/apps/index.html +892 -88
- nautobot/project-static/docs/apps/nautobot-apps.html +892 -88
- 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 +919 -120
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +904 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1618 -903
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +935 -144
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +977 -188
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +901 -99
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +897 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +991 -193
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +974 -131
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1078 -272
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1242 -334
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1727 -875
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1164 -381
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2088 -1374
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2246 -1422
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +912 -111
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +963 -163
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1010 -223
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1913 -1277
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1846 -1102
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +904 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2331 -1699
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1802 -1024
- nautobot/project-static/docs/development/apps/api/configuration-view.html +892 -88
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/global-search.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/graphql.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/index.html +942 -90
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +892 -88
- nautobot/project-static/docs/development/apps/api/prometheus.html +892 -88
- nautobot/project-static/docs/development/apps/api/setup.html +892 -88
- nautobot/project-static/docs/development/apps/api/testing.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/base-template.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/notes.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/urls.html +892 -88
- nautobot/project-static/docs/development/apps/index.html +892 -88
- nautobot/project-static/docs/development/apps/migration/code-updates.html +892 -88
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +892 -88
- nautobot/project-static/docs/development/apps/migration/from-v1.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +892 -88
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +892 -88
- nautobot/project-static/docs/development/core/application-registry.html +892 -88
- nautobot/project-static/docs/development/core/best-practices.html +893 -88
- nautobot/project-static/docs/development/core/bootstrap-ui.html +892 -88
- nautobot/project-static/docs/development/core/caching.html +892 -88
- nautobot/project-static/docs/development/core/controllers.html +892 -88
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +892 -88
- nautobot/project-static/docs/development/core/generic-views.html +892 -88
- nautobot/project-static/docs/development/core/getting-started.html +892 -88
- nautobot/project-static/docs/development/core/homepage.html +892 -88
- nautobot/project-static/docs/development/core/index.html +892 -88
- nautobot/project-static/docs/development/core/model-checklist.html +901 -89
- nautobot/project-static/docs/development/core/model-features.html +892 -88
- nautobot/project-static/docs/development/core/natural-keys.html +892 -88
- nautobot/project-static/docs/development/core/navigation-menu.html +892 -88
- nautobot/project-static/docs/development/core/release-checklist.html +895 -91
- nautobot/project-static/docs/development/core/role-internals.html +892 -88
- nautobot/project-static/docs/development/core/settings.html +892 -88
- nautobot/project-static/docs/development/core/style-guide.html +893 -89
- nautobot/project-static/docs/development/core/templates.html +904 -89
- nautobot/project-static/docs/development/core/testing.html +892 -88
- nautobot/project-static/docs/development/core/user-preferences.html +892 -88
- nautobot/project-static/docs/development/index.html +892 -88
- nautobot/project-static/docs/development/jobs/index.html +893 -89
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +892 -88
- nautobot/project-static/docs/index.html +892 -88
- 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 +900 -89
- nautobot/project-static/docs/overview/design_philosophy.html +892 -88
- nautobot/project-static/docs/release-notes/index.html +1129 -92
- nautobot/project-static/docs/release-notes/version-1.0.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.1.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.2.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.3.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.4.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.5.html +893 -89
- nautobot/project-static/docs/release-notes/version-1.6.html +893 -89
- nautobot/project-static/docs/release-notes/version-2.0.html +892 -88
- nautobot/project-static/docs/release-notes/version-2.1.html +892 -88
- nautobot/project-static/docs/release-notes/version-2.2.html +895 -91
- 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 +331 -256
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +992 -174
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +896 -88
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +944 -153
- nautobot/project-static/docs/user-guide/administration/installation/index.html +901 -93
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +934 -122
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +954 -157
- nautobot/project-static/docs/user-guide/administration/installation/services.html +913 -112
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +908 -99
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +892 -88
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +892 -88
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +892 -88
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +893 -89
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +893 -89
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +896 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +895 -91
- 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 +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +913 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +914 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +936 -118
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +928 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +937 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +923 -115
- 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 +916 -112
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +940 -83
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +943 -86
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +921 -103
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +929 -125
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +922 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +936 -88
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +897 -89
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +897 -89
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +901 -96
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +897 -89
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +892 -88
- 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 +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +892 -88
- nautobot/project-static/docs/user-guide/index.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1258 -785
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +896 -88
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +950 -121
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +892 -88
- 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 +10 -3
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/METADATA +20 -18
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/RECORD +677 -557
- 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.9.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.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):
|
|
@@ -1680,26 +2254,430 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1680
2254
|
PowerPort.objects.create(device=device, name="Power Port 2")
|
|
1681
2255
|
PowerPort.objects.create(device=device, name="Power Port 3")
|
|
1682
2256
|
|
|
1683
|
-
url = reverse("dcim:device_powerports", kwargs={"pk": device.pk})
|
|
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")
|
|
2638
|
+
|
|
2639
|
+
url = reverse("dcim:module_consoleports", kwargs={"pk": module.pk})
|
|
2640
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2641
|
+
|
|
2642
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2643
|
+
def test_module_consoleserverports(self):
|
|
2644
|
+
module = Module.objects.first()
|
|
2645
|
+
|
|
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")
|
|
2649
|
+
|
|
2650
|
+
url = reverse("dcim:module_consoleserverports", kwargs={"pk": module.pk})
|
|
2651
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
2652
|
+
|
|
2653
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2654
|
+
def test_module_powerports(self):
|
|
2655
|
+
module = Module.objects.first()
|
|
2656
|
+
|
|
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")
|
|
2660
|
+
|
|
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
|
|
|
@@ -1789,160 +2767,59 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1789
2767
|
sorted(interface_ips),
|
|
1790
2768
|
)
|
|
1791
2769
|
|
|
1792
|
-
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
1793
|
-
assign_ip_form_data["pk"] = []
|
|
1794
|
-
assign_ip_request = {
|
|
1795
|
-
"path": reverse("ipam:ipaddress_assign")
|
|
1796
|
-
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
1797
|
-
"data": post_data(assign_ip_form_data),
|
|
1798
|
-
}
|
|
1799
|
-
response = self.client.post(**assign_ip_request, follow=True)
|
|
1800
|
-
self.assertHttpStatus(response, 200)
|
|
1801
|
-
self.assertIn(
|
|
1802
|
-
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
1803
|
-
)
|
|
1804
|
-
|
|
1805
2770
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1806
|
-
def
|
|
1807
|
-
|
|
2771
|
+
def test_module_rearports(self):
|
|
2772
|
+
module = Module.objects.first()
|
|
1808
2773
|
|
|
1809
|
-
RearPort.objects.create(
|
|
1810
|
-
RearPort.objects.create(
|
|
1811
|
-
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")
|
|
1812
2777
|
|
|
1813
|
-
url = reverse("dcim:
|
|
2778
|
+
url = reverse("dcim:module_rearports", kwargs={"pk": module.pk})
|
|
1814
2779
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1815
2780
|
|
|
1816
2781
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1817
|
-
def
|
|
1818
|
-
|
|
2782
|
+
def test_module_frontports(self):
|
|
2783
|
+
module = Module.objects.first()
|
|
1819
2784
|
rear_ports = (
|
|
1820
|
-
RearPort.objects.create(
|
|
1821
|
-
RearPort.objects.create(
|
|
1822
|
-
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"),
|
|
1823
2788
|
)
|
|
1824
2789
|
|
|
1825
2790
|
FrontPort.objects.create(
|
|
1826
|
-
|
|
2791
|
+
module=module,
|
|
1827
2792
|
name="Front Port 1",
|
|
1828
2793
|
rear_port=rear_ports[0],
|
|
1829
2794
|
rear_port_position=1,
|
|
1830
2795
|
)
|
|
1831
2796
|
FrontPort.objects.create(
|
|
1832
|
-
|
|
2797
|
+
module=module,
|
|
1833
2798
|
name="Front Port 2",
|
|
1834
2799
|
rear_port=rear_ports[1],
|
|
1835
2800
|
rear_port_position=1,
|
|
1836
2801
|
)
|
|
1837
2802
|
FrontPort.objects.create(
|
|
1838
|
-
|
|
2803
|
+
module=module,
|
|
1839
2804
|
name="Front Port 3",
|
|
1840
2805
|
rear_port=rear_ports[2],
|
|
1841
2806
|
rear_port_position=1,
|
|
1842
2807
|
)
|
|
1843
2808
|
|
|
1844
|
-
url = reverse("dcim:
|
|
1845
|
-
self.assertHttpStatus(self.client.get(url), 200)
|
|
1846
|
-
|
|
1847
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1848
|
-
def test_device_devicebays(self):
|
|
1849
|
-
device = Device.objects.first()
|
|
1850
|
-
|
|
1851
|
-
# Device Bay 1 was already created in setUpTestData()
|
|
1852
|
-
DeviceBay.objects.create(device=device, name="Device Bay 2")
|
|
1853
|
-
DeviceBay.objects.create(device=device, name="Device Bay 3")
|
|
1854
|
-
|
|
1855
|
-
url = reverse("dcim:device_devicebays", kwargs={"pk": device.pk})
|
|
2809
|
+
url = reverse("dcim:module_frontports", kwargs={"pk": module.pk})
|
|
1856
2810
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1857
2811
|
|
|
1858
2812
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1859
|
-
def
|
|
1860
|
-
|
|
2813
|
+
def test_module_modulebays(self):
|
|
2814
|
+
module = Module.objects.first()
|
|
1861
2815
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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")
|
|
1865
2819
|
|
|
1866
|
-
url = reverse("dcim:
|
|
2820
|
+
url = reverse("dcim:module_modulebays", kwargs={"pk": module.pk})
|
|
1867
2821
|
self.assertHttpStatus(self.client.get(url), 200)
|
|
1868
2822
|
|
|
1869
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1870
|
-
def test_device_primary_ips(self):
|
|
1871
|
-
"""Test assigning a primary IP to a device."""
|
|
1872
|
-
self.add_permissions("dcim.change_device")
|
|
1873
|
-
|
|
1874
|
-
# Create an interface and assign an IP to it.
|
|
1875
|
-
device = Device.objects.filter(interfaces__isnull=False).first()
|
|
1876
|
-
interface = device.interfaces.first()
|
|
1877
|
-
namespace = Namespace.objects.first()
|
|
1878
|
-
Prefix.objects.create(prefix="1.2.3.0/24", namespace=namespace, status=self.prefix_status)
|
|
1879
|
-
ip_address = IPAddress.objects.create(address="1.2.3.4/32", namespace=namespace, status=self.ipaddr_status)
|
|
1880
|
-
interface.ip_addresses.add(ip_address)
|
|
1881
|
-
|
|
1882
|
-
# Dupe the form data and populated primary_ip4 w/ ip_address
|
|
1883
|
-
form_data = self.form_data.copy()
|
|
1884
|
-
form_data["primary_ip4"] = ip_address.pk
|
|
1885
|
-
# Assert that update succeeds.
|
|
1886
|
-
request = {
|
|
1887
|
-
"path": self._get_url("edit", device),
|
|
1888
|
-
"data": post_data(form_data),
|
|
1889
|
-
}
|
|
1890
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1891
|
-
self.assertInstanceEqual(self._get_queryset().order_by("last_updated").last(), form_data)
|
|
1892
|
-
|
|
1893
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1894
|
-
def test_local_config_context_schema_validation_pass(self):
|
|
1895
|
-
"""
|
|
1896
|
-
Given a config context schema
|
|
1897
|
-
And a device with local context that conforms to that schema
|
|
1898
|
-
Assert that the local context passes schema validation via full_clean()
|
|
1899
|
-
"""
|
|
1900
|
-
schema = ConfigContextSchema.objects.create(
|
|
1901
|
-
name="Schema 1",
|
|
1902
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "string"}}},
|
|
1903
|
-
)
|
|
1904
|
-
self.add_permissions("dcim.add_device")
|
|
1905
|
-
|
|
1906
|
-
form_data = self.form_data.copy()
|
|
1907
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1908
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1909
|
-
|
|
1910
|
-
# Try POST with model-level permission
|
|
1911
|
-
request = {
|
|
1912
|
-
"path": self._get_url("add"),
|
|
1913
|
-
"data": post_data(form_data),
|
|
1914
|
-
}
|
|
1915
|
-
self.assertHttpStatus(self.client.post(**request), 302)
|
|
1916
|
-
self.assertEqual(
|
|
1917
|
-
self._get_queryset().get(name="Device X").local_config_context_schema.pk,
|
|
1918
|
-
schema.pk,
|
|
1919
|
-
)
|
|
1920
|
-
|
|
1921
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1922
|
-
def test_local_config_context_schema_validation_fails(self):
|
|
1923
|
-
"""
|
|
1924
|
-
Given a config context schema
|
|
1925
|
-
And a device with local context that *does not* conform to that schema
|
|
1926
|
-
Assert that the local context fails schema validation via full_clean()
|
|
1927
|
-
"""
|
|
1928
|
-
schema = ConfigContextSchema.objects.create(
|
|
1929
|
-
name="Schema 1",
|
|
1930
|
-
data_schema={"type": "object", "properties": {"foo": {"type": "integer"}}},
|
|
1931
|
-
)
|
|
1932
|
-
self.add_permissions("dcim.add_device")
|
|
1933
|
-
|
|
1934
|
-
form_data = self.form_data.copy()
|
|
1935
|
-
form_data["local_config_context_schema"] = schema.pk
|
|
1936
|
-
form_data["local_config_context_data"] = '{"foo": "bar"}'
|
|
1937
|
-
|
|
1938
|
-
# Try POST with model-level permission
|
|
1939
|
-
request = {
|
|
1940
|
-
"path": self._get_url("add"),
|
|
1941
|
-
"data": post_data(form_data),
|
|
1942
|
-
}
|
|
1943
|
-
self.assertHttpStatus(self.client.post(**request), 200)
|
|
1944
|
-
self.assertEqual(self._get_queryset().filter(name="Device X").count(), 0)
|
|
1945
|
-
|
|
1946
2823
|
|
|
1947
2824
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1948
2825
|
model = ConsolePort
|
|
@@ -1983,6 +2860,15 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
1983
2860
|
"description": "New description",
|
|
1984
2861
|
}
|
|
1985
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
|
+
|
|
1986
2872
|
|
|
1987
2873
|
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
1988
2874
|
model = ConsoleServerPort
|
|
@@ -2022,6 +2908,15 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2022
2908
|
"description": "New description",
|
|
2023
2909
|
}
|
|
2024
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
|
+
|
|
2025
2920
|
|
|
2026
2921
|
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2027
2922
|
model = PowerPort
|
|
@@ -2066,12 +2961,22 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2066
2961
|
"description": "New description",
|
|
2067
2962
|
}
|
|
2068
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
|
+
|
|
2069
2973
|
|
|
2070
2974
|
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2071
2975
|
model = PowerOutlet
|
|
2072
2976
|
|
|
2073
2977
|
@classmethod
|
|
2074
2978
|
def setUpTestData(cls):
|
|
2979
|
+
PowerOutlet.objects.all().delete()
|
|
2075
2980
|
device = create_test_device("Device 1")
|
|
2076
2981
|
|
|
2077
2982
|
powerports = (
|
|
@@ -2124,12 +3029,23 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2124
3029
|
"description": "New description",
|
|
2125
3030
|
}
|
|
2126
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
|
+
|
|
2127
3042
|
|
|
2128
3043
|
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2129
3044
|
model = Interface
|
|
2130
3045
|
|
|
2131
3046
|
@classmethod
|
|
2132
3047
|
def setUpTestData(cls):
|
|
3048
|
+
Interface.objects.all().delete()
|
|
2133
3049
|
device = create_test_device("Device 1")
|
|
2134
3050
|
vrfs = list(VRF.objects.all()[:3])
|
|
2135
3051
|
for vrf in vrfs:
|
|
@@ -2137,22 +3053,24 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2137
3053
|
|
|
2138
3054
|
statuses = Status.objects.get_for_model(Interface)
|
|
2139
3055
|
status_active = statuses[0]
|
|
2140
|
-
|
|
3056
|
+
role = Role.objects.get_for_model(Interface).first()
|
|
2141
3057
|
interfaces = (
|
|
2142
|
-
Interface.objects.create(device=device, name="Interface
|
|
2143
|
-
Interface.objects.create(device=device, name="Interface
|
|
2144
|
-
Interface.objects.create(device=device, name="Interface
|
|
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),
|
|
2145
3061
|
Interface.objects.create(
|
|
2146
3062
|
device=device,
|
|
2147
3063
|
name="LAG",
|
|
2148
3064
|
status=status_active,
|
|
2149
3065
|
type=InterfaceTypeChoices.TYPE_LAG,
|
|
3066
|
+
role=role,
|
|
2150
3067
|
),
|
|
2151
3068
|
Interface.objects.create(
|
|
2152
3069
|
device=device,
|
|
2153
3070
|
name="BRIDGE",
|
|
2154
3071
|
status=status_active,
|
|
2155
3072
|
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
3073
|
+
role=role,
|
|
2156
3074
|
),
|
|
2157
3075
|
)
|
|
2158
3076
|
cls.lag_interface = interfaces[3]
|
|
@@ -2199,6 +3117,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2199
3117
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2200
3118
|
"enabled": False,
|
|
2201
3119
|
"status": status_active.pk,
|
|
3120
|
+
"role": role.pk,
|
|
2202
3121
|
"lag": interfaces[3].pk,
|
|
2203
3122
|
"mac_address": EUI("01:02:03:04:05:06"),
|
|
2204
3123
|
"mtu": 2000,
|
|
@@ -2227,6 +3146,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2227
3146
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2228
3147
|
"tags": [t.pk for t in Tag.objects.get_for_model(Interface)],
|
|
2229
3148
|
"status": status_active.pk,
|
|
3149
|
+
"role": role.pk,
|
|
2230
3150
|
"vrf": vrfs[0].pk,
|
|
2231
3151
|
}
|
|
2232
3152
|
|
|
@@ -2235,6 +3155,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2235
3155
|
"name_pattern": "Interface [4-6]",
|
|
2236
3156
|
"label_pattern": "Interface Number [4-6]",
|
|
2237
3157
|
"status": status_active.pk,
|
|
3158
|
+
"role": role.pk,
|
|
2238
3159
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2239
3160
|
"enabled": True,
|
|
2240
3161
|
"mtu": 1500,
|
|
@@ -2257,9 +3178,21 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2257
3178
|
"untagged_vlan": vlans[0].pk,
|
|
2258
3179
|
"tagged_vlans": [v.pk for v in vlans[1:4]],
|
|
2259
3180
|
"status": status_active.pk,
|
|
3181
|
+
"role": role.pk,
|
|
2260
3182
|
"vrf": vrfs[2].pk,
|
|
2261
3183
|
}
|
|
2262
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
|
+
|
|
2263
3196
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2264
3197
|
def test_create_virtual_interface_with_parent_lag(self):
|
|
2265
3198
|
"""https://github.com/nautobot/nautobot/issues/4436."""
|
|
@@ -2305,18 +3238,66 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2305
3238
|
cls.device = device
|
|
2306
3239
|
|
|
2307
3240
|
rearports = (
|
|
2308
|
-
RearPort.objects.create(
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
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
|
+
),
|
|
2314
3277
|
)
|
|
2315
3278
|
|
|
2316
3279
|
frontports = (
|
|
2317
|
-
FrontPort.objects.create(
|
|
2318
|
-
|
|
2319
|
-
|
|
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
|
+
),
|
|
2320
3301
|
)
|
|
2321
3302
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2322
3303
|
cls.selected_objects = frontports
|
|
@@ -2346,6 +3327,18 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2346
3327
|
"description": "New description",
|
|
2347
3328
|
}
|
|
2348
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
|
+
|
|
2349
3342
|
@unittest.skip("No DeviceBulkAddFrontPortView exists at present")
|
|
2350
3343
|
def test_bulk_add_component(self):
|
|
2351
3344
|
pass
|
|
@@ -2359,9 +3352,24 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2359
3352
|
device = create_test_device("Device 1")
|
|
2360
3353
|
|
|
2361
3354
|
rearports = (
|
|
2362
|
-
RearPort.objects.create(
|
|
2363
|
-
|
|
2364
|
-
|
|
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
|
+
),
|
|
2365
3373
|
)
|
|
2366
3374
|
# Required by ViewTestCases.DeviceComponentViewTestCase.test_bulk_rename
|
|
2367
3375
|
cls.selected_objects = rearports
|
|
@@ -2390,6 +3398,17 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2390
3398
|
"description": "New description",
|
|
2391
3399
|
}
|
|
2392
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
|
+
|
|
2393
3412
|
|
|
2394
3413
|
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2395
3414
|
model = DeviceBay
|
|
@@ -2428,6 +3447,113 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2428
3447
|
"description": "New description",
|
|
2429
3448
|
}
|
|
2430
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
|
+
|
|
2431
3557
|
|
|
2432
3558
|
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
2433
3559
|
model = InventoryItem
|
|
@@ -2480,6 +3606,14 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|
|
2480
3606
|
"software_version": software_versions[2].pk,
|
|
2481
3607
|
}
|
|
2482
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
|
+
|
|
2483
3617
|
def test_table_with_indentation_is_removed_on_filter_or_sort(self):
|
|
2484
3618
|
self.skipTest("InventoryItem table has no implementation of indentation.")
|
|
2485
3619
|
|
|
@@ -2540,73 +3674,73 @@ class CableTestCase(
|
|
|
2540
3674
|
interfaces = (
|
|
2541
3675
|
Interface.objects.create(
|
|
2542
3676
|
device=devices[0],
|
|
2543
|
-
name="Interface
|
|
3677
|
+
name="Interface A1",
|
|
2544
3678
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2545
3679
|
status=interface_status,
|
|
2546
3680
|
),
|
|
2547
3681
|
Interface.objects.create(
|
|
2548
3682
|
device=devices[0],
|
|
2549
|
-
name="Interface
|
|
3683
|
+
name="Interface A2",
|
|
2550
3684
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2551
3685
|
status=interface_status,
|
|
2552
3686
|
),
|
|
2553
3687
|
Interface.objects.create(
|
|
2554
3688
|
device=devices[0],
|
|
2555
|
-
name="Interface
|
|
3689
|
+
name="Interface A3",
|
|
2556
3690
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2557
3691
|
status=interface_status,
|
|
2558
3692
|
),
|
|
2559
3693
|
Interface.objects.create(
|
|
2560
3694
|
device=devices[1],
|
|
2561
|
-
name="Interface
|
|
3695
|
+
name="Interface A1",
|
|
2562
3696
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2563
3697
|
status=interface_status,
|
|
2564
3698
|
),
|
|
2565
3699
|
Interface.objects.create(
|
|
2566
3700
|
device=devices[1],
|
|
2567
|
-
name="Interface
|
|
3701
|
+
name="Interface A2",
|
|
2568
3702
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2569
3703
|
status=interface_status,
|
|
2570
3704
|
),
|
|
2571
3705
|
Interface.objects.create(
|
|
2572
3706
|
device=devices[1],
|
|
2573
|
-
name="Interface
|
|
3707
|
+
name="Interface A3",
|
|
2574
3708
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2575
3709
|
status=interface_status,
|
|
2576
3710
|
),
|
|
2577
3711
|
Interface.objects.create(
|
|
2578
3712
|
device=devices[2],
|
|
2579
|
-
name="Interface
|
|
3713
|
+
name="Interface A1",
|
|
2580
3714
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2581
3715
|
status=interface_status,
|
|
2582
3716
|
),
|
|
2583
3717
|
Interface.objects.create(
|
|
2584
3718
|
device=devices[2],
|
|
2585
|
-
name="Interface
|
|
3719
|
+
name="Interface A2",
|
|
2586
3720
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2587
3721
|
status=interface_status,
|
|
2588
3722
|
),
|
|
2589
3723
|
Interface.objects.create(
|
|
2590
3724
|
device=devices[2],
|
|
2591
|
-
name="Interface
|
|
3725
|
+
name="Interface A3",
|
|
2592
3726
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2593
3727
|
status=interface_status,
|
|
2594
3728
|
),
|
|
2595
3729
|
Interface.objects.create(
|
|
2596
3730
|
device=devices[3],
|
|
2597
|
-
name="Interface
|
|
3731
|
+
name="Interface A1",
|
|
2598
3732
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2599
3733
|
status=interface_status,
|
|
2600
3734
|
),
|
|
2601
3735
|
Interface.objects.create(
|
|
2602
3736
|
device=devices[3],
|
|
2603
|
-
name="Interface
|
|
3737
|
+
name="Interface A2",
|
|
2604
3738
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2605
3739
|
status=interface_status,
|
|
2606
3740
|
),
|
|
2607
3741
|
Interface.objects.create(
|
|
2608
3742
|
device=devices[3],
|
|
2609
|
-
name="Interface
|
|
3743
|
+
name="Interface A3",
|
|
2610
3744
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2611
3745
|
status=interface_status,
|
|
2612
3746
|
),
|
|
@@ -2758,6 +3892,9 @@ class ConsoleConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2758
3892
|
def _get_base_url(self):
|
|
2759
3893
|
return "dcim:console_connections_{}"
|
|
2760
3894
|
|
|
3895
|
+
def _get_queryset(self):
|
|
3896
|
+
return ConsolePort.objects.filter(cable__isnull=False)
|
|
3897
|
+
|
|
2761
3898
|
def get_list_url(self):
|
|
2762
3899
|
return "/dcim/console-connections/"
|
|
2763
3900
|
|
|
@@ -2810,15 +3947,18 @@ class PowerConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2810
3947
|
Test the PowerConnectionsListView.
|
|
2811
3948
|
"""
|
|
2812
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
|
+
|
|
2813
3956
|
def get_list_url(self):
|
|
2814
3957
|
return "/dcim/power-connections/"
|
|
2815
3958
|
|
|
2816
3959
|
def get_title(self):
|
|
2817
3960
|
return "Power Connections"
|
|
2818
3961
|
|
|
2819
|
-
def _get_base_url(self):
|
|
2820
|
-
return "dcim:power_connections_{}"
|
|
2821
|
-
|
|
2822
3962
|
def get_list_view(self):
|
|
2823
3963
|
return PowerConnectionsListView
|
|
2824
3964
|
|
|
@@ -2875,6 +4015,9 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2875
4015
|
def _get_base_url(self):
|
|
2876
4016
|
return "dcim:interface_connections_{}"
|
|
2877
4017
|
|
|
4018
|
+
def _get_queryset(self):
|
|
4019
|
+
return Interface.objects.filter(cable__isnull=False)
|
|
4020
|
+
|
|
2878
4021
|
def get_list_url(self):
|
|
2879
4022
|
return "/dcim/interface-connections/"
|
|
2880
4023
|
|
|
@@ -2895,22 +4038,25 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2895
4038
|
device_2 = create_test_device("Device 2")
|
|
2896
4039
|
|
|
2897
4040
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
4041
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
2898
4042
|
cls.interfaces = (
|
|
2899
4043
|
Interface.objects.create(
|
|
2900
4044
|
device=device_1,
|
|
2901
|
-
name="Interface
|
|
4045
|
+
name="Interface A1",
|
|
2902
4046
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2903
4047
|
status=interface_status,
|
|
4048
|
+
role=interface_role,
|
|
2904
4049
|
),
|
|
2905
4050
|
Interface.objects.create(
|
|
2906
4051
|
device=device_1,
|
|
2907
|
-
name="Interface
|
|
4052
|
+
name="Interface A2",
|
|
2908
4053
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2909
4054
|
status=interface_status,
|
|
4055
|
+
role=interface_role,
|
|
2910
4056
|
),
|
|
2911
4057
|
Interface.objects.create(
|
|
2912
4058
|
device=device_1,
|
|
2913
|
-
name="Interface
|
|
4059
|
+
name="Interface A3",
|
|
2914
4060
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2915
4061
|
status=interface_status,
|
|
2916
4062
|
),
|
|
@@ -2918,9 +4064,10 @@ class InterfaceConnectionsTestCase(ViewTestCases.ListObjectsViewTestCase):
|
|
|
2918
4064
|
|
|
2919
4065
|
cls.device_2_interface = Interface.objects.create(
|
|
2920
4066
|
device=device_2,
|
|
2921
|
-
name="Interface
|
|
4067
|
+
name="Interface A1",
|
|
2922
4068
|
type=InterfaceTypeChoices.TYPE_1GE_SFP,
|
|
2923
4069
|
status=interface_status,
|
|
4070
|
+
role=interface_role,
|
|
2924
4071
|
)
|
|
2925
4072
|
rearport = RearPort.objects.create(device=device_2, type=PortTypeChoices.TYPE_8P8C)
|
|
2926
4073
|
|
|
@@ -3042,6 +4189,28 @@ class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3042
4189
|
"domain": "domain-x",
|
|
3043
4190
|
}
|
|
3044
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
|
+
|
|
3045
4214
|
def test_device_column_visible(self):
|
|
3046
4215
|
"""
|
|
3047
4216
|
This checks whether the device column on a device's interfaces
|
|
@@ -3326,11 +4495,11 @@ class InterfaceRedundancyGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3326
4495
|
status=status_active,
|
|
3327
4496
|
)
|
|
3328
4497
|
intf_status = Status.objects.get_for_model(Interface).first()
|
|
3329
|
-
|
|
4498
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
3330
4499
|
cls.interfaces = (
|
|
3331
|
-
Interface.objects.create(device=device, name="Interface
|
|
3332
|
-
Interface.objects.create(device=device, name="Interface
|
|
3333
|
-
Interface.objects.create(device=device, name="Interface
|
|
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),
|
|
3334
4503
|
)
|
|
3335
4504
|
|
|
3336
4505
|
cls.form_data = {
|