nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +111 -0
- nautobot/cloud/filters.py +184 -0
- nautobot/cloud/forms.py +333 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +247 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +173 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +113 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +96 -28
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +34 -4
- nautobot/core/jobs/__init__.py +17 -6
- nautobot/core/jobs/cleanup.py +100 -0
- nautobot/core/jobs/groups.py +38 -0
- nautobot/core/management/commands/generate_test_data.py +116 -3
- nautobot/core/models/__init__.py +34 -9
- nautobot/core/models/generics.py +19 -3
- nautobot/core/models/name_color_content_types.py +7 -28
- nautobot/core/models/querysets.py +4 -3
- nautobot/core/models/tree_queries.py +1 -1
- nautobot/core/models/utils.py +21 -5
- nautobot/core/settings.py +4 -30
- nautobot/core/settings.yaml +34 -27
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +127 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +13 -23
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +51 -13
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
- nautobot/core/tests/integration/test_swagger.py +1 -1
- nautobot/core/tests/nautobot_config.py +0 -1
- nautobot/core/tests/runner.py +2 -2
- nautobot/core/tests/test_api.py +1 -0
- nautobot/core/tests/test_authentication.py +7 -2
- nautobot/core/tests/test_filters.py +11 -9
- nautobot/core/tests/test_forms.py +9 -0
- nautobot/core/tests/test_graphql.py +27 -16
- nautobot/core/tests/test_jobs.py +123 -74
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +118 -66
- nautobot/core/views/mixins.py +104 -35
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +79 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +249 -18
- nautobot/dcim/filters/__init__.py +369 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +817 -109
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +861 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +275 -106
- nautobot/dcim/models/devices.py +466 -13
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +231 -59
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +15 -7
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +691 -208
- nautobot/dcim/tests/test_filters.py +836 -217
- nautobot/dcim/tests/test_models.py +1072 -39
- nautobot/dcim/tests/test_views.py +1488 -358
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1107 -120
- nautobot/extras/__init__.py +0 -1
- nautobot/extras/api/serializers.py +115 -3
- nautobot/extras/api/urls.py +12 -0
- nautobot/extras/api/views.py +125 -7
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +422 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +17 -4
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +16 -13
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
- nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
- nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
- nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
- nautobot/extras/migrations/0111_metadata.py +162 -0
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
- nautobot/extras/migrations/0113_saved_views.py +77 -0
- nautobot/extras/models/__init__.py +15 -1
- nautobot/extras/models/change_logging.py +3 -3
- nautobot/extras/models/contacts.py +4 -0
- nautobot/extras/models/customfields.py +18 -3
- nautobot/extras/models/groups.py +389 -225
- nautobot/extras/models/jobs.py +4 -84
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +116 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +96 -114
- nautobot/extras/tables.py +171 -47
- nautobot/extras/templates/extras/dynamicgroup.html +44 -15
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
- nautobot/extras/templates/extras/job.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +0 -11
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +501 -22
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +4 -4
- nautobot/extras/tests/test_models.py +499 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +565 -28
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +56 -45
- nautobot/extras/views.py +585 -96
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +14 -1
- nautobot/ipam/forms.py +9 -5
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +23 -9
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +1 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +8 -3
- nautobot/ipam/tests/test_querysets.py +1 -1
- nautobot/ipam/tests/test_views.py +3 -2
- nautobot/ipam/urls.py +2 -2
- nautobot/ipam/views.py +25 -28
- nautobot/project-static/css/base.css +20 -1
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +884 -80
- nautobot/project-static/docs/apps/index.html +884 -80
- nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
- nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
- nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
- nautobot/project-static/docs/development/apps/api/setup.html +884 -80
- nautobot/project-static/docs/development/apps/api/testing.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
- nautobot/project-static/docs/development/apps/index.html +884 -80
- nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
- nautobot/project-static/docs/development/core/application-registry.html +884 -80
- nautobot/project-static/docs/development/core/best-practices.html +885 -80
- nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
- nautobot/project-static/docs/development/core/caching.html +884 -80
- nautobot/project-static/docs/development/core/controllers.html +884 -80
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
- nautobot/project-static/docs/development/core/generic-views.html +884 -80
- nautobot/project-static/docs/development/core/getting-started.html +884 -80
- nautobot/project-static/docs/development/core/homepage.html +884 -80
- nautobot/project-static/docs/development/core/index.html +884 -91
- nautobot/project-static/docs/development/core/model-checklist.html +887 -81
- nautobot/project-static/docs/development/core/model-features.html +884 -80
- nautobot/project-static/docs/development/core/natural-keys.html +884 -80
- nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
- nautobot/project-static/docs/development/core/release-checklist.html +887 -83
- nautobot/project-static/docs/development/core/role-internals.html +884 -80
- nautobot/project-static/docs/development/core/settings.html +884 -80
- nautobot/project-static/docs/development/core/style-guide.html +885 -81
- nautobot/project-static/docs/development/core/templates.html +896 -81
- nautobot/project-static/docs/development/core/testing.html +884 -80
- nautobot/project-static/docs/development/core/user-preferences.html +884 -80
- nautobot/project-static/docs/development/index.html +884 -80
- nautobot/project-static/docs/development/jobs/index.html +1247 -457
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
- nautobot/project-static/docs/index.html +13 -8228
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
- nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
- nautobot/project-static/docs/models/dcim/module.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
- nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
- nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
- nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
- nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
- nautobot/project-static/docs/models/extras/role.html +15 -0
- nautobot/project-static/docs/models/extras/savedview.html +15 -0
- nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
- nautobot/project-static/docs/models/extras/status.html +15 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +892 -81
- nautobot/project-static/docs/overview/design_philosophy.html +886 -82
- nautobot/project-static/docs/overview/index.html +9032 -13
- nautobot/project-static/docs/release-notes/index.html +887 -83
- nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
- nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
- nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
- nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
- nautobot/project-static/docs/requirements.txt +4 -4
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +335 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
- nautobot/project-static/docs/user-guide/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
- nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +22 -18
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/tables.py +15 -5
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
nautobot/dcim/tests/test_api.py
CHANGED
|
@@ -12,10 +12,13 @@ from rest_framework import status
|
|
|
12
12
|
from nautobot.core.testing import APITestCase, APIViewTestCases
|
|
13
13
|
from nautobot.core.testing.utils import generate_random_device_asset_tag_of_specified_size
|
|
14
14
|
from nautobot.dcim.choices import (
|
|
15
|
+
ConsolePortTypeChoices,
|
|
15
16
|
InterfaceModeChoices,
|
|
16
17
|
InterfaceTypeChoices,
|
|
17
18
|
PortTypeChoices,
|
|
18
19
|
PowerFeedTypeChoices,
|
|
20
|
+
PowerOutletTypeChoices,
|
|
21
|
+
PowerPortTypeChoices,
|
|
19
22
|
SoftwareImageFileHashingAlgorithmChoices,
|
|
20
23
|
SubdeviceRoleChoices,
|
|
21
24
|
)
|
|
@@ -43,6 +46,10 @@ from nautobot.dcim.models import (
|
|
|
43
46
|
Location,
|
|
44
47
|
LocationType,
|
|
45
48
|
Manufacturer,
|
|
49
|
+
Module,
|
|
50
|
+
ModuleBay,
|
|
51
|
+
ModuleBayTemplate,
|
|
52
|
+
ModuleType,
|
|
46
53
|
Platform,
|
|
47
54
|
PowerFeed,
|
|
48
55
|
PowerOutlet,
|
|
@@ -130,7 +137,7 @@ class Mixins:
|
|
|
130
137
|
@classmethod
|
|
131
138
|
def setUpTestData(cls):
|
|
132
139
|
super().setUpTestData()
|
|
133
|
-
cls.device_type = DeviceType.objects.
|
|
140
|
+
cls.device_type = DeviceType.objects.first()
|
|
134
141
|
cls.manufacturer = cls.device_type.manufacturer
|
|
135
142
|
cls.location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
|
|
136
143
|
cls.device_role = Role.objects.get_for_model(Device).first()
|
|
@@ -142,6 +149,8 @@ class Mixins:
|
|
|
142
149
|
location=cls.location,
|
|
143
150
|
status=cls.device_status,
|
|
144
151
|
)
|
|
152
|
+
cls.module = Module.objects.first()
|
|
153
|
+
cls.module_type = cls.module.module_type
|
|
145
154
|
|
|
146
155
|
class BasePortTestMixin(ComponentTraceMixin, BaseComponentTestMixin):
|
|
147
156
|
"""Mixin class for all `FooPort` tests."""
|
|
@@ -167,6 +176,166 @@ class Mixins:
|
|
|
167
176
|
]
|
|
168
177
|
)
|
|
169
178
|
|
|
179
|
+
class ModularDeviceComponentMixin:
|
|
180
|
+
modular_component_create_data = {}
|
|
181
|
+
device_field = "device" # field name for the parent device
|
|
182
|
+
module_field = "module" # field name for the parent module
|
|
183
|
+
update_data = {"label": "updated label", "description": "updated description"}
|
|
184
|
+
|
|
185
|
+
def test_module_device_validation(self):
|
|
186
|
+
"""Assert that a modular component can have a module or a device but not both."""
|
|
187
|
+
|
|
188
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
189
|
+
data = {
|
|
190
|
+
self.module_field: self.module.pk,
|
|
191
|
+
self.device_field: self.device.pk,
|
|
192
|
+
"name": "test parent module validation",
|
|
193
|
+
**self.modular_component_create_data,
|
|
194
|
+
}
|
|
195
|
+
url = self._get_list_url()
|
|
196
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
197
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
198
|
+
self.assertEqual(
|
|
199
|
+
response.json(),
|
|
200
|
+
{"non_field_errors": [f"Only one of {self.device_field} or {self.module_field} must be set"]},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
data.pop(self.module_field)
|
|
204
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
205
|
+
|
|
206
|
+
data.pop(self.device_field)
|
|
207
|
+
data[self.module_field] = self.module.pk
|
|
208
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
209
|
+
|
|
210
|
+
data.pop(self.module_field)
|
|
211
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
212
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
213
|
+
self.assertEqual(
|
|
214
|
+
response.json(),
|
|
215
|
+
{"__all__": [f"Either {self.device_field} or {self.module_field} must be set"]},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
def test_module_device_name_unique_validation(self):
|
|
219
|
+
"""Assert uniqueness constraint is enforced for (device,name) and (module,name) fields."""
|
|
220
|
+
|
|
221
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
222
|
+
modules = Module.objects.all()[:2]
|
|
223
|
+
data = {
|
|
224
|
+
self.module_field: modules[0].pk,
|
|
225
|
+
"name": "test modular device component parent validation",
|
|
226
|
+
**self.modular_component_create_data,
|
|
227
|
+
}
|
|
228
|
+
url = self._get_list_url()
|
|
229
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
230
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
231
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
232
|
+
self.assertEqual(
|
|
233
|
+
response.json(),
|
|
234
|
+
{"non_field_errors": [f"The fields {self.module_field}, name must make a unique set."]},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# same name, different module works
|
|
238
|
+
data[self.module_field] = modules[1].pk
|
|
239
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
240
|
+
|
|
241
|
+
devices = Device.objects.all()[:2]
|
|
242
|
+
data = {
|
|
243
|
+
self.device_field: devices[0].pk,
|
|
244
|
+
"name": "test modular device component parent validation",
|
|
245
|
+
**self.modular_component_create_data,
|
|
246
|
+
}
|
|
247
|
+
url = self._get_list_url()
|
|
248
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
249
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
250
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
251
|
+
self.assertEqual(
|
|
252
|
+
response.json(),
|
|
253
|
+
{"non_field_errors": [f"The fields {self.device_field}, name must make a unique set."]},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# same name, different device works
|
|
257
|
+
data[self.device_field] = devices[1].pk
|
|
258
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
259
|
+
|
|
260
|
+
class ModularDeviceComponentTemplateMixin:
|
|
261
|
+
modular_component_create_data = {}
|
|
262
|
+
update_data = {"label": "updated label", "description": "updated description"}
|
|
263
|
+
|
|
264
|
+
def test_module_type_device_type_validation(self):
|
|
265
|
+
"""Assert that a modular component template can have a module_type or a device_type but not both."""
|
|
266
|
+
|
|
267
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
268
|
+
data = {
|
|
269
|
+
"module_type": self.module_type.pk,
|
|
270
|
+
"device_type": self.device_type.pk,
|
|
271
|
+
"name": "test parent module_type validation",
|
|
272
|
+
**self.modular_component_create_data,
|
|
273
|
+
}
|
|
274
|
+
url = self._get_list_url()
|
|
275
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
276
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
277
|
+
self.assertEqual(
|
|
278
|
+
response.json(),
|
|
279
|
+
{"non_field_errors": ["Only one of device_type or module_type must be set"]},
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
data.pop("module_type")
|
|
283
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
284
|
+
|
|
285
|
+
data.pop("device_type")
|
|
286
|
+
data["module_type"] = self.module_type.pk
|
|
287
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
288
|
+
|
|
289
|
+
data.pop("module_type")
|
|
290
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
291
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
292
|
+
self.assertEqual(
|
|
293
|
+
response.json(),
|
|
294
|
+
{"__all__": ["Either device_type or module_type must be set"]},
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def test_module_type_device_type_name_unique_validation(self):
|
|
298
|
+
"""Assert uniqueness constraint is enforced for (device_type,name) and (module_type,name) fields."""
|
|
299
|
+
|
|
300
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
301
|
+
module_types = ModuleType.objects.all()[:2]
|
|
302
|
+
data = {
|
|
303
|
+
"module_type": module_types[0].pk,
|
|
304
|
+
"name": "test modular device component template parent validation",
|
|
305
|
+
**self.modular_component_create_data,
|
|
306
|
+
}
|
|
307
|
+
url = self._get_list_url()
|
|
308
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
309
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
310
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
311
|
+
self.assertEqual(
|
|
312
|
+
response.json(),
|
|
313
|
+
{"non_field_errors": ["The fields module_type, name must make a unique set."]},
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# same name, different module_type works
|
|
317
|
+
data["module_type"] = module_types[1].pk
|
|
318
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
319
|
+
|
|
320
|
+
device_types = DeviceType.objects.all()[:2]
|
|
321
|
+
data = {
|
|
322
|
+
"device_type": device_types[0].pk,
|
|
323
|
+
"name": "test modular device component template parent validation",
|
|
324
|
+
**self.modular_component_create_data,
|
|
325
|
+
}
|
|
326
|
+
url = self._get_list_url()
|
|
327
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
328
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
329
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
330
|
+
self.assertEqual(
|
|
331
|
+
response.json(),
|
|
332
|
+
{"non_field_errors": ["The fields device_type, name must make a unique set."]},
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# same name, different device_type works
|
|
336
|
+
data["device_type"] = device_types[1].pk
|
|
337
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
338
|
+
|
|
170
339
|
|
|
171
340
|
class LocationTypeTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModelAPIViewTestCaseMixin):
|
|
172
341
|
model = LocationType
|
|
@@ -302,7 +471,7 @@ class LocationTest(APIViewTestCases.APIViewTestCase, APIViewTestCases.TreeModelA
|
|
|
302
471
|
# Attempt to create new location with blank time_zone attr.
|
|
303
472
|
response = self.client.post(url, **self.header, data=location, format="json")
|
|
304
473
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
305
|
-
self.assertEqual(response.json()["time_zone"], ["
|
|
474
|
+
self.assertEqual(response.json()["time_zone"], ["This field may not be blank."])
|
|
306
475
|
|
|
307
476
|
def test_time_zone_field_post_valid(self):
|
|
308
477
|
"""
|
|
@@ -802,15 +971,17 @@ class ManufacturerTest(APIViewTestCases.APIViewTestCase):
|
|
|
802
971
|
"description": "New description",
|
|
803
972
|
}
|
|
804
973
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
974
|
+
def get_deletable_object(self):
|
|
975
|
+
mf = Manufacturer.objects.create(name="Deletable Manufacturer")
|
|
976
|
+
return mf
|
|
977
|
+
|
|
978
|
+
def get_deletable_object_pks(self):
|
|
979
|
+
mfs = [
|
|
980
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 1"),
|
|
981
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 2"),
|
|
982
|
+
Manufacturer.objects.create(name="Deletable Manufacturer 3"),
|
|
983
|
+
]
|
|
984
|
+
return [mf.pk for mf in mfs]
|
|
814
985
|
|
|
815
986
|
|
|
816
987
|
class DeviceTypeTest(Mixins.SoftwareImageFileRelatedModelMixin, APIViewTestCases.APIViewTestCase):
|
|
@@ -847,24 +1018,52 @@ class DeviceTypeTest(Mixins.SoftwareImageFileRelatedModelMixin, APIViewTestCases
|
|
|
847
1018
|
]
|
|
848
1019
|
|
|
849
1020
|
|
|
850
|
-
class
|
|
1021
|
+
class ModuleTypeTest(APIViewTestCases.APIViewTestCase):
|
|
1022
|
+
model = ModuleType
|
|
1023
|
+
bulk_update_data = {
|
|
1024
|
+
"part_number": "ABC123",
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
@classmethod
|
|
1028
|
+
def setUpTestData(cls):
|
|
1029
|
+
manufacturer_id = Manufacturer.objects.first().pk
|
|
1030
|
+
|
|
1031
|
+
cls.create_data = [
|
|
1032
|
+
{
|
|
1033
|
+
"manufacturer": manufacturer_id,
|
|
1034
|
+
"model": "Module Type 1",
|
|
1035
|
+
"part_number": "123456",
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
"manufacturer": manufacturer_id,
|
|
1039
|
+
"model": "Module Type 2",
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
"manufacturer": manufacturer_id,
|
|
1043
|
+
"model": "Module Type 3",
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
"manufacturer": manufacturer_id,
|
|
1047
|
+
"model": "Module Type 4",
|
|
1048
|
+
},
|
|
1049
|
+
]
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
class ConsolePortTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
851
1053
|
model = ConsolePortTemplate
|
|
1054
|
+
modular_component_create_data = {"type": ConsolePortTypeChoices.TYPE_RJ45}
|
|
852
1055
|
|
|
853
1056
|
@classmethod
|
|
854
1057
|
def setUpTestData(cls):
|
|
855
1058
|
super().setUpTestData()
|
|
856
1059
|
|
|
857
|
-
ConsolePortTemplate.objects.create(device_type=cls.device_type, name="Console Port Template 1")
|
|
858
|
-
ConsolePortTemplate.objects.create(device_type=cls.device_type, name="Console Port Template 2")
|
|
859
|
-
ConsolePortTemplate.objects.create(device_type=cls.device_type, name="Console Port Template 3")
|
|
860
|
-
|
|
861
1060
|
cls.create_data = [
|
|
862
1061
|
{
|
|
863
1062
|
"device_type": cls.device_type.pk,
|
|
864
1063
|
"name": "Console Port Template 4",
|
|
865
1064
|
},
|
|
866
1065
|
{
|
|
867
|
-
"
|
|
1066
|
+
"module_type": cls.module_type.pk,
|
|
868
1067
|
"name": "Console Port Template 5",
|
|
869
1068
|
},
|
|
870
1069
|
{
|
|
@@ -874,24 +1073,21 @@ class ConsolePortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
874
1073
|
]
|
|
875
1074
|
|
|
876
1075
|
|
|
877
|
-
class ConsoleServerPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
1076
|
+
class ConsoleServerPortTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
878
1077
|
model = ConsoleServerPortTemplate
|
|
1078
|
+
modular_component_create_data = {"type": ConsolePortTypeChoices.TYPE_RJ45}
|
|
879
1079
|
|
|
880
1080
|
@classmethod
|
|
881
1081
|
def setUpTestData(cls):
|
|
882
1082
|
super().setUpTestData()
|
|
883
1083
|
|
|
884
|
-
ConsoleServerPortTemplate.objects.create(device_type=cls.device_type, name="Console Server Port Template 1")
|
|
885
|
-
ConsoleServerPortTemplate.objects.create(device_type=cls.device_type, name="Console Server Port Template 2")
|
|
886
|
-
ConsoleServerPortTemplate.objects.create(device_type=cls.device_type, name="Console Server Port Template 3")
|
|
887
|
-
|
|
888
1084
|
cls.create_data = [
|
|
889
1085
|
{
|
|
890
1086
|
"device_type": cls.device_type.pk,
|
|
891
1087
|
"name": "Console Server Port Template 4",
|
|
892
1088
|
},
|
|
893
1089
|
{
|
|
894
|
-
"
|
|
1090
|
+
"module_type": cls.module_type.pk,
|
|
895
1091
|
"name": "Console Server Port Template 5",
|
|
896
1092
|
},
|
|
897
1093
|
{
|
|
@@ -901,24 +1097,21 @@ class ConsoleServerPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
901
1097
|
]
|
|
902
1098
|
|
|
903
1099
|
|
|
904
|
-
class PowerPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
1100
|
+
class PowerPortTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
905
1101
|
model = PowerPortTemplate
|
|
1102
|
+
modular_component_create_data = {"type": PowerPortTypeChoices.TYPE_NEMA_1030P}
|
|
906
1103
|
|
|
907
1104
|
@classmethod
|
|
908
1105
|
def setUpTestData(cls):
|
|
909
1106
|
super().setUpTestData()
|
|
910
1107
|
|
|
911
|
-
PowerPortTemplate.objects.create(device_type=cls.device_type, name="Power Port Template 1")
|
|
912
|
-
PowerPortTemplate.objects.create(device_type=cls.device_type, name="Power Port Template 2")
|
|
913
|
-
PowerPortTemplate.objects.create(device_type=cls.device_type, name="Power Port Template 3")
|
|
914
|
-
|
|
915
1108
|
cls.create_data = [
|
|
916
1109
|
{
|
|
917
1110
|
"device_type": cls.device_type.pk,
|
|
918
1111
|
"name": "Power Port Template 4",
|
|
919
1112
|
},
|
|
920
1113
|
{
|
|
921
|
-
"
|
|
1114
|
+
"module_type": cls.module_type.pk,
|
|
922
1115
|
"name": "Power Port Template 5",
|
|
923
1116
|
},
|
|
924
1117
|
{
|
|
@@ -928,25 +1121,22 @@ class PowerPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
928
1121
|
]
|
|
929
1122
|
|
|
930
1123
|
|
|
931
|
-
class PowerOutletTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
1124
|
+
class PowerOutletTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
932
1125
|
model = PowerOutletTemplate
|
|
933
1126
|
choices_fields = ["feed_leg", "type"]
|
|
1127
|
+
modular_component_create_data = {"type": PowerOutletTypeChoices.TYPE_IEC_C13}
|
|
934
1128
|
|
|
935
1129
|
@classmethod
|
|
936
1130
|
def setUpTestData(cls):
|
|
937
1131
|
super().setUpTestData()
|
|
938
1132
|
|
|
939
|
-
PowerOutletTemplate.objects.create(device_type=cls.device_type, name="Power Outlet Template 1")
|
|
940
|
-
PowerOutletTemplate.objects.create(device_type=cls.device_type, name="Power Outlet Template 2")
|
|
941
|
-
PowerOutletTemplate.objects.create(device_type=cls.device_type, name="Power Outlet Template 3")
|
|
942
|
-
|
|
943
1133
|
cls.create_data = [
|
|
944
1134
|
{
|
|
945
1135
|
"device_type": cls.device_type.pk,
|
|
946
1136
|
"name": "Power Outlet Template 4",
|
|
947
1137
|
},
|
|
948
1138
|
{
|
|
949
|
-
"
|
|
1139
|
+
"module_type": cls.module_type.pk,
|
|
950
1140
|
"name": "Power Outlet Template 5",
|
|
951
1141
|
},
|
|
952
1142
|
{
|
|
@@ -956,17 +1146,13 @@ class PowerOutletTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
956
1146
|
]
|
|
957
1147
|
|
|
958
1148
|
|
|
959
|
-
class InterfaceTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
1149
|
+
class InterfaceTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
960
1150
|
model = InterfaceTemplate
|
|
1151
|
+
modular_component_create_data = {"type": InterfaceTypeChoices.TYPE_1GE_FIXED}
|
|
961
1152
|
|
|
962
1153
|
@classmethod
|
|
963
1154
|
def setUpTestData(cls):
|
|
964
1155
|
super().setUpTestData()
|
|
965
|
-
|
|
966
|
-
InterfaceTemplate.objects.create(device_type=cls.device_type, name="Interface Template 1", type="1000base-t")
|
|
967
|
-
InterfaceTemplate.objects.create(device_type=cls.device_type, name="Interface Template 2", type="1000base-t")
|
|
968
|
-
InterfaceTemplate.objects.create(device_type=cls.device_type, name="Interface Template 3", type="1000base-t")
|
|
969
|
-
|
|
970
1156
|
cls.create_data = [
|
|
971
1157
|
{
|
|
972
1158
|
"device_type": cls.device_type.pk,
|
|
@@ -974,7 +1160,7 @@ class InterfaceTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
974
1160
|
"type": "1000base-t",
|
|
975
1161
|
},
|
|
976
1162
|
{
|
|
977
|
-
"
|
|
1163
|
+
"module_type": cls.module_type.pk,
|
|
978
1164
|
"name": "Interface Template 5",
|
|
979
1165
|
"type": "1000base-t",
|
|
980
1166
|
},
|
|
@@ -988,61 +1174,21 @@ class InterfaceTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
988
1174
|
|
|
989
1175
|
class FrontPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
990
1176
|
model = FrontPortTemplate
|
|
1177
|
+
update_data = {"label": "updated label", "description": "updated description"}
|
|
991
1178
|
|
|
992
1179
|
@classmethod
|
|
993
1180
|
def setUpTestData(cls):
|
|
994
1181
|
super().setUpTestData()
|
|
995
1182
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1001
|
-
),
|
|
1002
|
-
RearPortTemplate.objects.create(
|
|
1003
|
-
device_type=cls.device_type,
|
|
1004
|
-
name="Rear Port Template 2",
|
|
1005
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1006
|
-
),
|
|
1007
|
-
RearPortTemplate.objects.create(
|
|
1008
|
-
device_type=cls.device_type,
|
|
1009
|
-
name="Rear Port Template 3",
|
|
1010
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1011
|
-
),
|
|
1012
|
-
RearPortTemplate.objects.create(
|
|
1013
|
-
device_type=cls.device_type,
|
|
1014
|
-
name="Rear Port Template 4",
|
|
1015
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1016
|
-
),
|
|
1017
|
-
RearPortTemplate.objects.create(
|
|
1018
|
-
device_type=cls.device_type,
|
|
1019
|
-
name="Rear Port Template 5",
|
|
1020
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1021
|
-
),
|
|
1022
|
-
RearPortTemplate.objects.create(
|
|
1023
|
-
device_type=cls.device_type,
|
|
1024
|
-
name="Rear Port Template 6",
|
|
1025
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1026
|
-
),
|
|
1183
|
+
cls.module_type = ModuleType.objects.first()
|
|
1184
|
+
cls.module_rear_port_templates = (
|
|
1185
|
+
RearPortTemplate.objects.create(module_type=cls.module_type, name="Test FrontPort RP1", positions=100),
|
|
1186
|
+
RearPortTemplate.objects.create(module_type=cls.module_type, name="Test FrontPort RP2", positions=100),
|
|
1027
1187
|
)
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
device_type=cls.device_type,
|
|
1031
|
-
name="
|
|
1032
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1033
|
-
rear_port_template=rear_port_templates[0],
|
|
1034
|
-
)
|
|
1035
|
-
FrontPortTemplate.objects.create(
|
|
1036
|
-
device_type=cls.device_type,
|
|
1037
|
-
name="Front Port Template 2",
|
|
1038
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1039
|
-
rear_port_template=rear_port_templates[1],
|
|
1040
|
-
)
|
|
1041
|
-
FrontPortTemplate.objects.create(
|
|
1042
|
-
device_type=cls.device_type,
|
|
1043
|
-
name="Front Port Template 3",
|
|
1044
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1045
|
-
rear_port_template=rear_port_templates[2],
|
|
1188
|
+
cls.device_type = DeviceType.objects.first()
|
|
1189
|
+
cls.device_rear_port_templates = (
|
|
1190
|
+
RearPortTemplate.objects.create(device_type=cls.device_type, name="Test FrontPort RP3", positions=100),
|
|
1191
|
+
RearPortTemplate.objects.create(device_type=cls.device_type, name="Test FrontPort RP4", positions=100),
|
|
1046
1192
|
)
|
|
1047
1193
|
|
|
1048
1194
|
cls.create_data = [
|
|
@@ -1050,49 +1196,114 @@ class FrontPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
1050
1196
|
"device_type": cls.device_type.pk,
|
|
1051
1197
|
"name": "Front Port Template 4",
|
|
1052
1198
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1053
|
-
"rear_port_template":
|
|
1199
|
+
"rear_port_template": cls.device_rear_port_templates[0].pk,
|
|
1054
1200
|
"rear_port_position": 1,
|
|
1055
1201
|
},
|
|
1056
1202
|
{
|
|
1057
1203
|
"device_type": cls.device_type.pk,
|
|
1058
1204
|
"name": "Front Port Template 5",
|
|
1059
1205
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1060
|
-
"rear_port_template":
|
|
1206
|
+
"rear_port_template": cls.device_rear_port_templates[1].pk,
|
|
1061
1207
|
"rear_port_position": 1,
|
|
1062
1208
|
},
|
|
1063
1209
|
{
|
|
1064
|
-
"
|
|
1210
|
+
"module_type": cls.module_type.pk,
|
|
1065
1211
|
"name": "Front Port Template 6",
|
|
1066
1212
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1067
|
-
"rear_port_template":
|
|
1213
|
+
"rear_port_template": cls.module_rear_port_templates[0].pk,
|
|
1068
1214
|
"rear_port_position": 1,
|
|
1069
1215
|
},
|
|
1070
1216
|
]
|
|
1071
1217
|
|
|
1218
|
+
def test_module_type_device_type_validation(self):
|
|
1219
|
+
"""Assert that a modular component template can have a module_type or a device_type but not both."""
|
|
1072
1220
|
|
|
1073
|
-
|
|
1221
|
+
self.add_permissions("dcim.add_frontporttemplate")
|
|
1222
|
+
data = {
|
|
1223
|
+
"module_type": self.module_type.pk,
|
|
1224
|
+
"device_type": self.device_type.pk,
|
|
1225
|
+
"name": "test parent module_type validation",
|
|
1226
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
1227
|
+
"rear_port_template": self.device_rear_port_templates[0].pk,
|
|
1228
|
+
"rear_port_position": 2,
|
|
1229
|
+
}
|
|
1230
|
+
url = self._get_list_url()
|
|
1231
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1232
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1233
|
+
self.assertEqual(
|
|
1234
|
+
response.json(),
|
|
1235
|
+
{"non_field_errors": ["Only one of device_type or module_type must be set"]},
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
data.pop("module_type")
|
|
1239
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1240
|
+
|
|
1241
|
+
data.pop("device_type")
|
|
1242
|
+
data["module_type"] = self.module_type.pk
|
|
1243
|
+
data["rear_port_template"] = self.module_rear_port_templates[0].pk
|
|
1244
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1245
|
+
|
|
1246
|
+
def test_module_type_device_type_name_unique_validation(self):
|
|
1247
|
+
"""Assert uniqueness constraint is enforced for (device_type,name) and (module_type,name) fields."""
|
|
1248
|
+
|
|
1249
|
+
self.add_permissions("dcim.add_frontporttemplate")
|
|
1250
|
+
data = {
|
|
1251
|
+
"module_type": self.module_type.pk,
|
|
1252
|
+
"name": "test modular device_type component parent validation",
|
|
1253
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
1254
|
+
"rear_port_template": self.module_rear_port_templates[0].pk,
|
|
1255
|
+
"rear_port_position": 2,
|
|
1256
|
+
}
|
|
1257
|
+
url = self._get_list_url()
|
|
1258
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1259
|
+
|
|
1260
|
+
data = {
|
|
1261
|
+
"module_type": self.module_type.pk,
|
|
1262
|
+
"name": "test modular device_type component parent validation",
|
|
1263
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
1264
|
+
"rear_port_template": self.module_rear_port_templates[1].pk,
|
|
1265
|
+
"rear_port_position": 2,
|
|
1266
|
+
}
|
|
1267
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1268
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1269
|
+
self.assertEqual(
|
|
1270
|
+
response.json(),
|
|
1271
|
+
{"non_field_errors": ["The fields module_type, name must make a unique set."]},
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
data = {
|
|
1275
|
+
"device_type": self.device_type.pk,
|
|
1276
|
+
"name": "test modular device_type component parent validation",
|
|
1277
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
1278
|
+
"rear_port_template": self.device_rear_port_templates[0].pk,
|
|
1279
|
+
"rear_port_position": 2,
|
|
1280
|
+
}
|
|
1281
|
+
url = self._get_list_url()
|
|
1282
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1283
|
+
|
|
1284
|
+
data = {
|
|
1285
|
+
"device_type": self.device_type.pk,
|
|
1286
|
+
"name": "test modular device_type component parent validation",
|
|
1287
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
1288
|
+
"rear_port_template": self.device_rear_port_templates[1].pk,
|
|
1289
|
+
"rear_port_position": 2,
|
|
1290
|
+
}
|
|
1291
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1292
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1293
|
+
self.assertEqual(
|
|
1294
|
+
response.json(),
|
|
1295
|
+
{"non_field_errors": ["The fields device_type, name must make a unique set."]},
|
|
1296
|
+
)
|
|
1297
|
+
|
|
1298
|
+
|
|
1299
|
+
class RearPortTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BasePortTemplateTestMixin):
|
|
1074
1300
|
model = RearPortTemplate
|
|
1301
|
+
modular_component_create_data = {"type": PortTypeChoices.TYPE_8P8C}
|
|
1075
1302
|
|
|
1076
1303
|
@classmethod
|
|
1077
1304
|
def setUpTestData(cls):
|
|
1078
1305
|
super().setUpTestData()
|
|
1079
1306
|
|
|
1080
|
-
RearPortTemplate.objects.create(
|
|
1081
|
-
device_type=cls.device_type,
|
|
1082
|
-
name="Rear Port Template 1",
|
|
1083
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1084
|
-
)
|
|
1085
|
-
RearPortTemplate.objects.create(
|
|
1086
|
-
device_type=cls.device_type,
|
|
1087
|
-
name="Rear Port Template 2",
|
|
1088
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1089
|
-
)
|
|
1090
|
-
RearPortTemplate.objects.create(
|
|
1091
|
-
device_type=cls.device_type,
|
|
1092
|
-
name="Rear Port Template 3",
|
|
1093
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1094
|
-
)
|
|
1095
|
-
|
|
1096
1307
|
cls.create_data = [
|
|
1097
1308
|
{
|
|
1098
1309
|
"device_type": cls.device_type.pk,
|
|
@@ -1100,12 +1311,12 @@ class RearPortTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
1100
1311
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1101
1312
|
},
|
|
1102
1313
|
{
|
|
1103
|
-
"
|
|
1314
|
+
"module_type": cls.module_type.pk,
|
|
1104
1315
|
"name": "Rear Port Template 5",
|
|
1105
1316
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1106
1317
|
},
|
|
1107
1318
|
{
|
|
1108
|
-
"
|
|
1319
|
+
"module_type": cls.module_type.pk,
|
|
1109
1320
|
"name": "Rear Port Template 6",
|
|
1110
1321
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1111
1322
|
},
|
|
@@ -1141,6 +1352,30 @@ class DeviceBayTemplateTest(Mixins.BasePortTemplateTestMixin):
|
|
|
1141
1352
|
]
|
|
1142
1353
|
|
|
1143
1354
|
|
|
1355
|
+
class ModuleBayTemplateTest(Mixins.ModularDeviceComponentTemplateMixin, Mixins.BaseComponentTestMixin):
|
|
1356
|
+
model = ModuleBayTemplate
|
|
1357
|
+
choices_fields = []
|
|
1358
|
+
|
|
1359
|
+
@classmethod
|
|
1360
|
+
def setUpTestData(cls):
|
|
1361
|
+
super().setUpTestData()
|
|
1362
|
+
|
|
1363
|
+
cls.create_data = [
|
|
1364
|
+
{
|
|
1365
|
+
"device_type": cls.device_type.pk,
|
|
1366
|
+
"name": "Test1",
|
|
1367
|
+
},
|
|
1368
|
+
{
|
|
1369
|
+
"module_type": cls.module_type.pk,
|
|
1370
|
+
"name": "Test2",
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"device_type": cls.device_type.pk,
|
|
1374
|
+
"name": "Test3",
|
|
1375
|
+
},
|
|
1376
|
+
]
|
|
1377
|
+
|
|
1378
|
+
|
|
1144
1379
|
class PlatformTest(APIViewTestCases.APIViewTestCase):
|
|
1145
1380
|
model = Platform
|
|
1146
1381
|
create_data = [
|
|
@@ -1462,120 +1697,238 @@ class DeviceTest(APIViewTestCases.APIViewTestCase):
|
|
|
1462
1697
|
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1463
1698
|
|
|
1464
1699
|
|
|
1465
|
-
class
|
|
1700
|
+
class ModuleTestCase(APIViewTestCases.APIViewTestCase):
|
|
1701
|
+
model = Module
|
|
1702
|
+
|
|
1703
|
+
@classmethod
|
|
1704
|
+
def setUpTestData(cls):
|
|
1705
|
+
cls.module_type = ModuleType.objects.first()
|
|
1706
|
+
cls.module_bay = ModuleBay.objects.filter(installed_module__isnull=True).first()
|
|
1707
|
+
cls.module_status = Status.objects.get_for_model(Module).first()
|
|
1708
|
+
cls.location = Location.objects.get_for_model(Module).first()
|
|
1709
|
+
cls.create_data = [
|
|
1710
|
+
{
|
|
1711
|
+
"module_type": cls.module_type.pk,
|
|
1712
|
+
"parent_module_bay": cls.module_bay.pk,
|
|
1713
|
+
"status": cls.module_status.pk,
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
"module_type": cls.module_type.pk,
|
|
1717
|
+
"location": cls.location.pk,
|
|
1718
|
+
"status": cls.module_status.pk,
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
"module_type": cls.module_type.pk,
|
|
1722
|
+
"location": cls.location.pk,
|
|
1723
|
+
"serial": "test module serial xyz",
|
|
1724
|
+
"asset_tag": "test module 2",
|
|
1725
|
+
"status": cls.module_status.pk,
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
"module_type": cls.module_type.pk,
|
|
1729
|
+
"location": cls.location.pk,
|
|
1730
|
+
"asset_tag": "Test Module 3",
|
|
1731
|
+
"status": cls.module_status.pk,
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
"module_type": cls.module_type.pk,
|
|
1735
|
+
"location": cls.location.pk,
|
|
1736
|
+
"serial": "test module serial abc",
|
|
1737
|
+
"status": cls.module_status.pk,
|
|
1738
|
+
},
|
|
1739
|
+
]
|
|
1740
|
+
cls.bulk_update_data = {
|
|
1741
|
+
"tenant": Tenant.objects.first().pk,
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
cls.update_data = {
|
|
1745
|
+
"serial": "new serial 789",
|
|
1746
|
+
"asset_tag": "new asset tag 789",
|
|
1747
|
+
"status": Status.objects.get_for_model(Module).last().pk,
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
def get_deletable_object_pks(self):
|
|
1751
|
+
# Since Modules and ModuleBays are nestable, we need to delete Modules that don't have any child Modules
|
|
1752
|
+
return Module.objects.exclude(module_bays__installed_module__isnull=False).values_list("pk", flat=True)[:3]
|
|
1753
|
+
|
|
1754
|
+
def test_parent_module_bay_location_validation(self):
|
|
1755
|
+
"""Assert that a module can have a parent_module_bay or a location but not both."""
|
|
1756
|
+
|
|
1757
|
+
self.add_permissions("dcim.add_module")
|
|
1758
|
+
data = {
|
|
1759
|
+
"module_type": self.module_type.pk,
|
|
1760
|
+
"location": self.location.pk,
|
|
1761
|
+
"parent_module_bay": self.module_bay.pk,
|
|
1762
|
+
"status": self.module_status.pk,
|
|
1763
|
+
}
|
|
1764
|
+
url = self._get_list_url()
|
|
1765
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1766
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1767
|
+
self.assertEqual(
|
|
1768
|
+
response.json(),
|
|
1769
|
+
{"non_field_errors": ["Only one of parent_module_bay or location must be set"]},
|
|
1770
|
+
)
|
|
1771
|
+
|
|
1772
|
+
data.pop("parent_module_bay")
|
|
1773
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1774
|
+
|
|
1775
|
+
data.pop("location")
|
|
1776
|
+
data["parent_module_bay"] = self.module_bay.pk
|
|
1777
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1778
|
+
|
|
1779
|
+
data.pop("parent_module_bay")
|
|
1780
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1781
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1782
|
+
self.assertEqual(
|
|
1783
|
+
response.json(),
|
|
1784
|
+
{"__all__": ["One of location or parent_module_bay must be set"]},
|
|
1785
|
+
)
|
|
1786
|
+
|
|
1787
|
+
def test_serial_module_type_unique_validation(self):
|
|
1788
|
+
self.add_permissions("dcim.add_module")
|
|
1789
|
+
data = {
|
|
1790
|
+
"module_type": self.module_type.pk,
|
|
1791
|
+
"location": self.location.pk,
|
|
1792
|
+
"status": self.module_status.pk,
|
|
1793
|
+
}
|
|
1794
|
+
url = self._get_list_url()
|
|
1795
|
+
# create multiple instances with null serial
|
|
1796
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1797
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1798
|
+
data["serial"] = ""
|
|
1799
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1800
|
+
data["serial"] = None
|
|
1801
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1802
|
+
|
|
1803
|
+
data["serial"] = "xyz"
|
|
1804
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1805
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1806
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1807
|
+
self.assertEqual(
|
|
1808
|
+
response.json(),
|
|
1809
|
+
{"non_field_errors": ["The fields module_type, serial must make a unique set."]},
|
|
1810
|
+
)
|
|
1811
|
+
|
|
1812
|
+
def test_asset_tag_unique_validation(self):
|
|
1813
|
+
self.add_permissions("dcim.add_module")
|
|
1814
|
+
data = {
|
|
1815
|
+
"module_type": self.module_type.pk,
|
|
1816
|
+
"location": self.location.pk,
|
|
1817
|
+
"status": self.module_status.pk,
|
|
1818
|
+
"asset_tag": "xyz123",
|
|
1819
|
+
}
|
|
1820
|
+
url = self._get_list_url()
|
|
1821
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1822
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1823
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
1824
|
+
self.assertEqual(
|
|
1825
|
+
response.json(),
|
|
1826
|
+
{"asset_tag": ["module with this Asset tag already exists."]},
|
|
1827
|
+
)
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
class ConsolePortTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1466
1831
|
model = ConsolePort
|
|
1467
1832
|
peer_termination_type = ConsoleServerPort
|
|
1833
|
+
modular_component_create_data = {"type": ConsolePortTypeChoices.TYPE_RJ45}
|
|
1468
1834
|
|
|
1469
1835
|
@classmethod
|
|
1470
1836
|
def setUpTestData(cls):
|
|
1471
1837
|
super().setUpTestData()
|
|
1472
1838
|
|
|
1473
|
-
ConsolePort.objects.create(device=cls.device, name="Console Port 1")
|
|
1474
|
-
ConsolePort.objects.create(device=cls.device, name="Console Port 2")
|
|
1475
|
-
ConsolePort.objects.create(device=cls.device, name="Console Port 3")
|
|
1476
|
-
|
|
1477
1839
|
cls.create_data = [
|
|
1478
1840
|
{
|
|
1479
1841
|
"device": cls.device.pk,
|
|
1480
|
-
"name": "Console Port
|
|
1842
|
+
"name": "Console Port 1",
|
|
1481
1843
|
},
|
|
1482
1844
|
{
|
|
1483
|
-
"
|
|
1484
|
-
"name": "Console Port
|
|
1845
|
+
"module": cls.module.pk,
|
|
1846
|
+
"name": "Console Port 2",
|
|
1485
1847
|
},
|
|
1486
1848
|
{
|
|
1487
1849
|
"device": cls.device.pk,
|
|
1488
|
-
"name": "Console Port
|
|
1850
|
+
"name": "Console Port 3",
|
|
1489
1851
|
},
|
|
1490
1852
|
]
|
|
1491
1853
|
|
|
1492
1854
|
|
|
1493
|
-
class ConsoleServerPortTest(Mixins.BasePortTestMixin):
|
|
1855
|
+
class ConsoleServerPortTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1494
1856
|
model = ConsoleServerPort
|
|
1495
1857
|
peer_termination_type = ConsolePort
|
|
1858
|
+
modular_component_create_data = {"type": ConsolePortTypeChoices.TYPE_RJ45}
|
|
1496
1859
|
|
|
1497
1860
|
@classmethod
|
|
1498
1861
|
def setUpTestData(cls):
|
|
1499
1862
|
super().setUpTestData()
|
|
1500
1863
|
|
|
1501
|
-
ConsoleServerPort.objects.create(device=cls.device, name="Console Server Port 1")
|
|
1502
|
-
ConsoleServerPort.objects.create(device=cls.device, name="Console Server Port 2")
|
|
1503
|
-
ConsoleServerPort.objects.create(device=cls.device, name="Console Server Port 3")
|
|
1504
|
-
|
|
1505
1864
|
cls.create_data = [
|
|
1506
1865
|
{
|
|
1507
1866
|
"device": cls.device.pk,
|
|
1508
|
-
"name": "Console Server Port
|
|
1867
|
+
"name": "Console Server Port 1",
|
|
1509
1868
|
},
|
|
1510
1869
|
{
|
|
1511
|
-
"
|
|
1512
|
-
"name": "Console Server Port
|
|
1870
|
+
"module": cls.module.pk,
|
|
1871
|
+
"name": "Console Server Port 2",
|
|
1513
1872
|
},
|
|
1514
1873
|
{
|
|
1515
1874
|
"device": cls.device.pk,
|
|
1516
|
-
"name": "Console Server Port
|
|
1875
|
+
"name": "Console Server Port 3",
|
|
1517
1876
|
},
|
|
1518
1877
|
]
|
|
1519
1878
|
|
|
1520
1879
|
|
|
1521
|
-
class PowerPortTest(Mixins.BasePortTestMixin):
|
|
1880
|
+
class PowerPortTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1522
1881
|
model = PowerPort
|
|
1523
1882
|
peer_termination_type = PowerOutlet
|
|
1883
|
+
modular_component_create_data = {"type": PowerPortTypeChoices.TYPE_NEMA_1030P}
|
|
1524
1884
|
|
|
1525
1885
|
@classmethod
|
|
1526
1886
|
def setUpTestData(cls):
|
|
1527
1887
|
super().setUpTestData()
|
|
1528
1888
|
|
|
1529
|
-
PowerPort.objects.create(device=cls.device, name="Power Port 1")
|
|
1530
|
-
PowerPort.objects.create(device=cls.device, name="Power Port 2")
|
|
1531
|
-
PowerPort.objects.create(device=cls.device, name="Power Port 3")
|
|
1532
|
-
|
|
1533
1889
|
cls.create_data = [
|
|
1534
1890
|
{
|
|
1535
1891
|
"device": cls.device.pk,
|
|
1536
|
-
"name": "Power Port
|
|
1892
|
+
"name": "Power Port 1",
|
|
1537
1893
|
},
|
|
1538
1894
|
{
|
|
1539
|
-
"
|
|
1540
|
-
"name": "Power Port
|
|
1895
|
+
"module": cls.module.pk,
|
|
1896
|
+
"name": "Power Port 2",
|
|
1541
1897
|
},
|
|
1542
1898
|
{
|
|
1543
1899
|
"device": cls.device.pk,
|
|
1544
|
-
"name": "Power Port
|
|
1900
|
+
"name": "Power Port 3",
|
|
1545
1901
|
},
|
|
1546
1902
|
]
|
|
1547
1903
|
|
|
1548
1904
|
|
|
1549
|
-
class PowerOutletTest(Mixins.BasePortTestMixin):
|
|
1905
|
+
class PowerOutletTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1550
1906
|
model = PowerOutlet
|
|
1551
1907
|
peer_termination_type = PowerPort
|
|
1552
1908
|
choices_fields = ["feed_leg", "type"]
|
|
1909
|
+
modular_component_create_data = {"type": PowerOutletTypeChoices.TYPE_IEC_C13}
|
|
1553
1910
|
|
|
1554
1911
|
@classmethod
|
|
1555
1912
|
def setUpTestData(cls):
|
|
1556
1913
|
super().setUpTestData()
|
|
1557
1914
|
|
|
1558
|
-
PowerOutlet.objects.create(device=cls.device, name="Power Outlet 1")
|
|
1559
|
-
PowerOutlet.objects.create(device=cls.device, name="Power Outlet 2")
|
|
1560
|
-
PowerOutlet.objects.create(device=cls.device, name="Power Outlet 3")
|
|
1561
|
-
|
|
1562
1915
|
cls.create_data = [
|
|
1563
1916
|
{
|
|
1564
1917
|
"device": cls.device.pk,
|
|
1565
|
-
"name": "Power Outlet
|
|
1918
|
+
"name": "Power Outlet 1",
|
|
1566
1919
|
},
|
|
1567
1920
|
{
|
|
1568
|
-
"
|
|
1569
|
-
"name": "Power Outlet
|
|
1921
|
+
"module": cls.module.pk,
|
|
1922
|
+
"name": "Power Outlet 2",
|
|
1570
1923
|
},
|
|
1571
1924
|
{
|
|
1572
1925
|
"device": cls.device.pk,
|
|
1573
|
-
"name": "Power Outlet
|
|
1926
|
+
"name": "Power Outlet 3",
|
|
1574
1927
|
},
|
|
1575
1928
|
]
|
|
1576
1929
|
|
|
1577
1930
|
|
|
1578
|
-
class InterfaceTest(Mixins.BasePortTestMixin):
|
|
1931
|
+
class InterfaceTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1579
1932
|
model = Interface
|
|
1580
1933
|
peer_termination_type = Interface
|
|
1581
1934
|
choices_fields = ["mode", "type"]
|
|
@@ -1584,7 +1937,10 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1584
1937
|
def setUpTestData(cls):
|
|
1585
1938
|
super().setUpTestData()
|
|
1586
1939
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
1587
|
-
|
|
1940
|
+
cls.modular_component_create_data = {
|
|
1941
|
+
"type": InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
1942
|
+
"status": interface_status.pk,
|
|
1943
|
+
}
|
|
1588
1944
|
cls.devices = (
|
|
1589
1945
|
cls.device,
|
|
1590
1946
|
Device.objects.create(
|
|
@@ -1611,48 +1967,54 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1611
1967
|
|
|
1612
1968
|
# Interfaces have special handling around the "Active" status so let's set our interfaces to something else.
|
|
1613
1969
|
non_default_status = Status.objects.get_for_model(Interface).exclude(name="Active").first()
|
|
1970
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
1614
1971
|
cls.interfaces = (
|
|
1615
1972
|
Interface.objects.create(
|
|
1616
1973
|
device=cls.devices[0],
|
|
1617
|
-
name="Interface 1",
|
|
1974
|
+
name="Test Interface 1",
|
|
1618
1975
|
type="1000base-t",
|
|
1619
1976
|
status=non_default_status,
|
|
1977
|
+
role=intf_role,
|
|
1620
1978
|
),
|
|
1621
1979
|
Interface.objects.create(
|
|
1622
1980
|
device=cls.devices[0],
|
|
1623
|
-
name="Interface 2",
|
|
1981
|
+
name="Test Interface 2",
|
|
1624
1982
|
type="1000base-t",
|
|
1625
1983
|
status=non_default_status,
|
|
1626
1984
|
),
|
|
1627
1985
|
Interface.objects.create(
|
|
1628
1986
|
device=cls.devices[0],
|
|
1629
|
-
name="Interface 3",
|
|
1987
|
+
name="Test Interface 3",
|
|
1630
1988
|
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
1631
1989
|
status=non_default_status,
|
|
1990
|
+
role=intf_role,
|
|
1632
1991
|
),
|
|
1633
1992
|
Interface.objects.create(
|
|
1634
1993
|
device=cls.devices[1],
|
|
1635
|
-
name="Interface 4",
|
|
1994
|
+
name="Test Interface 4",
|
|
1636
1995
|
type=InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
1637
1996
|
status=non_default_status,
|
|
1997
|
+
role=intf_role,
|
|
1638
1998
|
),
|
|
1639
1999
|
Interface.objects.create(
|
|
1640
2000
|
device=cls.devices[1],
|
|
1641
|
-
name="Interface 5",
|
|
2001
|
+
name="Test Interface 5",
|
|
1642
2002
|
type=InterfaceTypeChoices.TYPE_LAG,
|
|
1643
2003
|
status=non_default_status,
|
|
1644
2004
|
),
|
|
1645
2005
|
Interface.objects.create(
|
|
1646
2006
|
device=cls.devices[2],
|
|
1647
|
-
name="Interface 6",
|
|
2007
|
+
name="Test Interface 6",
|
|
1648
2008
|
type=InterfaceTypeChoices.TYPE_LAG,
|
|
1649
2009
|
status=non_default_status,
|
|
2010
|
+
role=intf_role,
|
|
1650
2011
|
),
|
|
1651
2012
|
Interface.objects.create(
|
|
1652
2013
|
device=cls.devices[2],
|
|
1653
|
-
name="Interface 7",
|
|
2014
|
+
name="Test Interface 7",
|
|
1654
2015
|
type=InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
1655
2016
|
status=non_default_status,
|
|
2017
|
+
role=intf_role,
|
|
1656
2018
|
),
|
|
1657
2019
|
)
|
|
1658
2020
|
|
|
@@ -1670,6 +2032,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1670
2032
|
"name": "Interface 8",
|
|
1671
2033
|
"type": "1000base-t",
|
|
1672
2034
|
"status": interface_status.pk,
|
|
2035
|
+
"role": intf_role.pk,
|
|
1673
2036
|
"mode": InterfaceModeChoices.MODE_TAGGED,
|
|
1674
2037
|
"tagged_vlans": [cls.vlans[0].pk, cls.vlans[1].pk],
|
|
1675
2038
|
"untagged_vlan": cls.vlans[2].pk,
|
|
@@ -1680,6 +2043,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1680
2043
|
"name": "Interface 9",
|
|
1681
2044
|
"type": "1000base-t",
|
|
1682
2045
|
"status": interface_status.pk,
|
|
2046
|
+
"role": intf_role.pk,
|
|
1683
2047
|
"mode": InterfaceModeChoices.MODE_TAGGED,
|
|
1684
2048
|
"bridge": cls.interfaces[3].pk,
|
|
1685
2049
|
"tagged_vlans": [cls.vlans[0].pk, cls.vlans[1].pk],
|
|
@@ -1730,6 +2094,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1730
2094
|
{
|
|
1731
2095
|
"device": cls.devices[0].pk,
|
|
1732
2096
|
"name": "interface test 1",
|
|
2097
|
+
"role": intf_role.pk,
|
|
1733
2098
|
"type": InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
1734
2099
|
"status": interface_status.pk,
|
|
1735
2100
|
"parent_interface": cls.interfaces[6].id, # do not belong to same device or vc
|
|
@@ -1741,6 +2106,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1741
2106
|
"device": cls.devices[0].pk,
|
|
1742
2107
|
"name": "interface test 2",
|
|
1743
2108
|
"type": InterfaceTypeChoices.TYPE_1GE_GBIC,
|
|
2109
|
+
"role": intf_role.pk,
|
|
1744
2110
|
"status": interface_status.pk,
|
|
1745
2111
|
"bridge": cls.interfaces[6].id, # does not belong to same device or vc
|
|
1746
2112
|
},
|
|
@@ -1840,6 +2206,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1840
2206
|
mode=InterfaceModeChoices.MODE_TAGGED,
|
|
1841
2207
|
type=InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
1842
2208
|
status=Status.objects.get_for_model(Interface).first(),
|
|
2209
|
+
role=Role.objects.get_for_model(Interface).first(),
|
|
1843
2210
|
)
|
|
1844
2211
|
interface.tagged_vlans.add(self.vlans[0])
|
|
1845
2212
|
payload = {"mode": None, "tagged_vlans": [self.vlans[2].pk]}
|
|
@@ -1871,6 +2238,7 @@ class InterfaceTest(Mixins.BasePortTestMixin):
|
|
|
1871
2238
|
class FrontPortTest(Mixins.BasePortTestMixin):
|
|
1872
2239
|
model = FrontPort
|
|
1873
2240
|
peer_termination_type = Interface
|
|
2241
|
+
update_data = {"label": "updated label", "description": "updated description"}
|
|
1874
2242
|
|
|
1875
2243
|
def test_trace(self):
|
|
1876
2244
|
"""FrontPorts don't support trace."""
|
|
@@ -1879,62 +2247,135 @@ class FrontPortTest(Mixins.BasePortTestMixin):
|
|
|
1879
2247
|
def setUpTestData(cls):
|
|
1880
2248
|
super().setUpTestData()
|
|
1881
2249
|
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
RearPort.objects.create(
|
|
1885
|
-
RearPort.objects.create(
|
|
1886
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 4", type=PortTypeChoices.TYPE_8P8C),
|
|
1887
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 5", type=PortTypeChoices.TYPE_8P8C),
|
|
1888
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 6", type=PortTypeChoices.TYPE_8P8C),
|
|
2250
|
+
cls.module = Module.objects.first()
|
|
2251
|
+
cls.module_rear_ports = (
|
|
2252
|
+
RearPort.objects.create(module=cls.module, name="Test FrontPort RP1", positions=100),
|
|
2253
|
+
RearPort.objects.create(module=cls.module, name="Test FrontPort RP2", positions=100),
|
|
1889
2254
|
)
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
device=cls.device,
|
|
1893
|
-
name="
|
|
1894
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1895
|
-
rear_port=rear_ports[0],
|
|
1896
|
-
)
|
|
1897
|
-
FrontPort.objects.create(
|
|
1898
|
-
device=cls.device,
|
|
1899
|
-
name="Front Port 2",
|
|
1900
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1901
|
-
rear_port=rear_ports[1],
|
|
1902
|
-
)
|
|
1903
|
-
FrontPort.objects.create(
|
|
1904
|
-
device=cls.device,
|
|
1905
|
-
name="Front Port 3",
|
|
1906
|
-
type=PortTypeChoices.TYPE_8P8C,
|
|
1907
|
-
rear_port=rear_ports[2],
|
|
2255
|
+
cls.device = Device.objects.first()
|
|
2256
|
+
cls.device_rear_ports = (
|
|
2257
|
+
RearPort.objects.create(device=cls.device, name="Test FrontPort RP3", positions=100),
|
|
2258
|
+
RearPort.objects.create(device=cls.device, name="Test FrontPort RP4", positions=100),
|
|
1908
2259
|
)
|
|
1909
2260
|
|
|
1910
2261
|
cls.create_data = [
|
|
1911
2262
|
{
|
|
1912
2263
|
"device": cls.device.pk,
|
|
1913
|
-
"name": "Front Port
|
|
2264
|
+
"name": "Front Port 1",
|
|
1914
2265
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1915
|
-
"rear_port":
|
|
2266
|
+
"rear_port": cls.device_rear_ports[0].pk,
|
|
1916
2267
|
"rear_port_position": 1,
|
|
1917
2268
|
},
|
|
1918
2269
|
{
|
|
1919
2270
|
"device": cls.device.pk,
|
|
1920
|
-
"name": "Front Port
|
|
2271
|
+
"name": "Front Port 2",
|
|
1921
2272
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1922
|
-
"rear_port":
|
|
2273
|
+
"rear_port": cls.device_rear_ports[1].pk,
|
|
1923
2274
|
"rear_port_position": 1,
|
|
1924
2275
|
},
|
|
1925
2276
|
{
|
|
1926
|
-
"
|
|
1927
|
-
"name": "Front Port
|
|
2277
|
+
"module": cls.module.pk,
|
|
2278
|
+
"name": "Front Port 3",
|
|
1928
2279
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1929
|
-
"rear_port":
|
|
2280
|
+
"rear_port": cls.module_rear_ports[0].pk,
|
|
1930
2281
|
"rear_port_position": 1,
|
|
1931
2282
|
},
|
|
1932
2283
|
]
|
|
1933
2284
|
|
|
2285
|
+
def test_module_device_validation(self):
|
|
2286
|
+
"""Assert that a modular component can have a module or a device but not both."""
|
|
2287
|
+
|
|
2288
|
+
self.add_permissions("dcim.add_frontport")
|
|
2289
|
+
data = {
|
|
2290
|
+
"module": self.module.pk,
|
|
2291
|
+
"device": self.device.pk,
|
|
2292
|
+
"name": "test parent module validation",
|
|
2293
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
2294
|
+
"rear_port": self.device_rear_ports[0].pk,
|
|
2295
|
+
"rear_port_position": 2,
|
|
2296
|
+
}
|
|
2297
|
+
url = self._get_list_url()
|
|
2298
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2299
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
2300
|
+
self.assertEqual(
|
|
2301
|
+
response.json(),
|
|
2302
|
+
{"non_field_errors": ["Only one of device or module must be set"]},
|
|
2303
|
+
)
|
|
2304
|
+
|
|
2305
|
+
data.pop("module")
|
|
2306
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
2307
|
+
|
|
2308
|
+
data.pop("device")
|
|
2309
|
+
data["module"] = self.module.pk
|
|
2310
|
+
data["rear_port"] = self.module_rear_ports[0].pk
|
|
2311
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
2312
|
+
|
|
2313
|
+
data.pop("module")
|
|
2314
|
+
data["rear_port_position"] = 3
|
|
2315
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2316
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
2317
|
+
self.assertEqual(
|
|
2318
|
+
response.json(),
|
|
2319
|
+
{"__all__": ["Either device or module must be set"]},
|
|
2320
|
+
)
|
|
2321
|
+
|
|
2322
|
+
def test_module_device_name_unique_validation(self):
|
|
2323
|
+
"""Assert uniqueness constraint is enforced for (device,name) and (module,name) fields."""
|
|
2324
|
+
|
|
2325
|
+
self.add_permissions("dcim.add_frontport")
|
|
2326
|
+
data = {
|
|
2327
|
+
"module": self.module.pk,
|
|
2328
|
+
"name": "test modular device component parent validation",
|
|
2329
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
2330
|
+
"rear_port": self.module_rear_ports[0].pk,
|
|
2331
|
+
"rear_port_position": 2,
|
|
2332
|
+
}
|
|
2333
|
+
url = self._get_list_url()
|
|
2334
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
2335
|
+
|
|
2336
|
+
data = {
|
|
2337
|
+
"module": self.module.pk,
|
|
2338
|
+
"name": "test modular device component parent validation",
|
|
2339
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
2340
|
+
"rear_port": self.module_rear_ports[1].pk,
|
|
2341
|
+
"rear_port_position": 2,
|
|
2342
|
+
}
|
|
2343
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2344
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
2345
|
+
self.assertEqual(
|
|
2346
|
+
response.json(),
|
|
2347
|
+
{"non_field_errors": ["The fields module, name must make a unique set."]},
|
|
2348
|
+
)
|
|
2349
|
+
|
|
2350
|
+
data = {
|
|
2351
|
+
"device": self.device.pk,
|
|
2352
|
+
"name": "test modular device component parent validation",
|
|
2353
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
2354
|
+
"rear_port": self.device_rear_ports[0].pk,
|
|
2355
|
+
"rear_port_position": 2,
|
|
2356
|
+
}
|
|
2357
|
+
url = self._get_list_url()
|
|
2358
|
+
self.assertHttpStatus(self.client.post(url, data, format="json", **self.header), status.HTTP_201_CREATED)
|
|
1934
2359
|
|
|
1935
|
-
|
|
2360
|
+
data = {
|
|
2361
|
+
"device": self.device.pk,
|
|
2362
|
+
"name": "test modular device component parent validation",
|
|
2363
|
+
"type": PortTypeChoices.TYPE_8P8C,
|
|
2364
|
+
"rear_port": self.device_rear_ports[1].pk,
|
|
2365
|
+
"rear_port_position": 2,
|
|
2366
|
+
}
|
|
2367
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
2368
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
2369
|
+
self.assertEqual(
|
|
2370
|
+
response.json(),
|
|
2371
|
+
{"non_field_errors": ["The fields device, name must make a unique set."]},
|
|
2372
|
+
)
|
|
2373
|
+
|
|
2374
|
+
|
|
2375
|
+
class RearPortTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin):
|
|
1936
2376
|
model = RearPort
|
|
1937
2377
|
peer_termination_type = Interface
|
|
2378
|
+
modular_component_create_data = {"type": PortTypeChoices.TYPE_8P8C}
|
|
1938
2379
|
|
|
1939
2380
|
def test_trace(self):
|
|
1940
2381
|
"""RearPorts don't support trace."""
|
|
@@ -1943,24 +2384,20 @@ class RearPortTest(Mixins.BasePortTestMixin):
|
|
|
1943
2384
|
def setUpTestData(cls):
|
|
1944
2385
|
super().setUpTestData()
|
|
1945
2386
|
|
|
1946
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 1", type=PortTypeChoices.TYPE_8P8C)
|
|
1947
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 2", type=PortTypeChoices.TYPE_8P8C)
|
|
1948
|
-
RearPort.objects.create(device=cls.device, name="Rear Port 3", type=PortTypeChoices.TYPE_8P8C)
|
|
1949
|
-
|
|
1950
2387
|
cls.create_data = [
|
|
1951
2388
|
{
|
|
1952
2389
|
"device": cls.device.pk,
|
|
1953
|
-
"name": "Rear Port
|
|
2390
|
+
"name": "Rear Port 1",
|
|
1954
2391
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1955
2392
|
},
|
|
1956
2393
|
{
|
|
1957
|
-
"
|
|
1958
|
-
"name": "Rear Port
|
|
2394
|
+
"module": cls.module.pk,
|
|
2395
|
+
"name": "Rear Port 2",
|
|
1959
2396
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1960
2397
|
},
|
|
1961
2398
|
{
|
|
1962
2399
|
"device": cls.device.pk,
|
|
1963
|
-
"name": "Rear Port
|
|
2400
|
+
"name": "Rear Port 3",
|
|
1964
2401
|
"type": PortTypeChoices.TYPE_8P8C,
|
|
1965
2402
|
},
|
|
1966
2403
|
]
|
|
@@ -2078,6 +2515,36 @@ class InventoryItemTest(Mixins.BaseComponentTestMixin, APIViewTestCases.TreeMode
|
|
|
2078
2515
|
pass
|
|
2079
2516
|
|
|
2080
2517
|
|
|
2518
|
+
class ModuleBayTest(Mixins.ModularDeviceComponentMixin, Mixins.BaseComponentTestMixin):
|
|
2519
|
+
model = ModuleBay
|
|
2520
|
+
choices_fields = []
|
|
2521
|
+
device_field = "parent_device"
|
|
2522
|
+
module_field = "parent_module"
|
|
2523
|
+
|
|
2524
|
+
@classmethod
|
|
2525
|
+
def setUpTestData(cls):
|
|
2526
|
+
super().setUpTestData()
|
|
2527
|
+
|
|
2528
|
+
cls.create_data = [
|
|
2529
|
+
{
|
|
2530
|
+
"parent_device": cls.device.pk,
|
|
2531
|
+
"name": "Test1",
|
|
2532
|
+
},
|
|
2533
|
+
{
|
|
2534
|
+
"parent_module": cls.module.pk,
|
|
2535
|
+
"name": "Test2",
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
"parent_device": cls.device.pk,
|
|
2539
|
+
"name": "Test3",
|
|
2540
|
+
},
|
|
2541
|
+
]
|
|
2542
|
+
|
|
2543
|
+
def get_deletable_object_pks(self):
|
|
2544
|
+
# Since Modules and ModuleBays are nestable, we need to delete ModuleBays that don't have any child ModuleBays
|
|
2545
|
+
return ModuleBay.objects.filter(installed_module__isnull=True).values_list("pk", flat=True)[:3]
|
|
2546
|
+
|
|
2547
|
+
|
|
2081
2548
|
class CableTest(Mixins.BaseComponentTestMixin):
|
|
2082
2549
|
model = Cable
|
|
2083
2550
|
bulk_update_data = {
|
|
@@ -2111,6 +2578,7 @@ class CableTest(Mixins.BaseComponentTestMixin):
|
|
|
2111
2578
|
|
|
2112
2579
|
interfaces = []
|
|
2113
2580
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
2581
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
2114
2582
|
for device in devices:
|
|
2115
2583
|
for i in range(0, 10):
|
|
2116
2584
|
interfaces.append(
|
|
@@ -2119,6 +2587,7 @@ class CableTest(Mixins.BaseComponentTestMixin):
|
|
|
2119
2587
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2120
2588
|
name=f"eth{i}",
|
|
2121
2589
|
status=interface_status,
|
|
2590
|
+
role=interface_role,
|
|
2122
2591
|
)
|
|
2123
2592
|
)
|
|
2124
2593
|
|
|
@@ -2197,8 +2666,16 @@ class ConnectedDeviceTest(APITestCase):
|
|
|
2197
2666
|
location=location,
|
|
2198
2667
|
)
|
|
2199
2668
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
2200
|
-
interface1 = Interface.objects.create(
|
|
2201
|
-
|
|
2669
|
+
interface1 = Interface.objects.create(
|
|
2670
|
+
device=self.device1,
|
|
2671
|
+
name="eth0",
|
|
2672
|
+
status=interface_status,
|
|
2673
|
+
)
|
|
2674
|
+
interface2 = Interface.objects.create(
|
|
2675
|
+
device=device2,
|
|
2676
|
+
name="eth0",
|
|
2677
|
+
status=interface_status,
|
|
2678
|
+
)
|
|
2202
2679
|
|
|
2203
2680
|
cable = Cable(termination_a=interface1, termination_b=interface2, status=cable_status)
|
|
2204
2681
|
cable.validated_save()
|
|
@@ -2313,6 +2790,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|
|
2313
2790
|
|
|
2314
2791
|
# Create 12 interfaces per device
|
|
2315
2792
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
2793
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
2316
2794
|
interfaces = []
|
|
2317
2795
|
for i, device in enumerate(devices):
|
|
2318
2796
|
for j in range(0, 13):
|
|
@@ -2323,6 +2801,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|
|
2323
2801
|
name=f"{i%3+1}/{j}",
|
|
2324
2802
|
type=InterfaceTypeChoices.TYPE_1GE_FIXED,
|
|
2325
2803
|
status=interface_status,
|
|
2804
|
+
role=interface_role,
|
|
2326
2805
|
)
|
|
2327
2806
|
)
|
|
2328
2807
|
|
|
@@ -2635,7 +3114,7 @@ class InterfaceRedundancyGroupTestCase(APIViewTestCases.APIViewTestCase):
|
|
|
2635
3114
|
|
|
2636
3115
|
interface_redundancy_groups = (
|
|
2637
3116
|
InterfaceRedundancyGroup(
|
|
2638
|
-
name="Interface Redundancy Group 1",
|
|
3117
|
+
name="Test Interface Redundancy Group 1",
|
|
2639
3118
|
protocol="hsrp",
|
|
2640
3119
|
status=statuses[0],
|
|
2641
3120
|
virtual_ip=None,
|
|
@@ -2643,7 +3122,7 @@ class InterfaceRedundancyGroupTestCase(APIViewTestCases.APIViewTestCase):
|
|
|
2643
3122
|
secrets_group=secrets_groups[0],
|
|
2644
3123
|
),
|
|
2645
3124
|
InterfaceRedundancyGroup(
|
|
2646
|
-
name="Interface Redundancy Group 2",
|
|
3125
|
+
name="Test Interface Redundancy Group 2",
|
|
2647
3126
|
protocol="carp",
|
|
2648
3127
|
status=statuses[1],
|
|
2649
3128
|
virtual_ip=ips[1],
|
|
@@ -2651,7 +3130,7 @@ class InterfaceRedundancyGroupTestCase(APIViewTestCases.APIViewTestCase):
|
|
|
2651
3130
|
secrets_group=secrets_groups[1],
|
|
2652
3131
|
),
|
|
2653
3132
|
InterfaceRedundancyGroup(
|
|
2654
|
-
name="Interface Redundancy Group 3",
|
|
3133
|
+
name="Test Interface Redundancy Group 3",
|
|
2655
3134
|
protocol="vrrp",
|
|
2656
3135
|
status=statuses[2],
|
|
2657
3136
|
virtual_ip=ips[2],
|
|
@@ -2678,19 +3157,19 @@ class InterfaceRedundancyGroupTestCase(APIViewTestCases.APIViewTestCase):
|
|
|
2678
3157
|
cls.interfaces = (
|
|
2679
3158
|
Interface.objects.create(
|
|
2680
3159
|
device=cls.device,
|
|
2681
|
-
name="Interface 1",
|
|
3160
|
+
name="Test Interface 1",
|
|
2682
3161
|
type="1000base-t",
|
|
2683
3162
|
status=non_default_status,
|
|
2684
3163
|
),
|
|
2685
3164
|
Interface.objects.create(
|
|
2686
3165
|
device=cls.device,
|
|
2687
|
-
name="Interface 2",
|
|
3166
|
+
name="Test Interface 2",
|
|
2688
3167
|
type="1000base-t",
|
|
2689
3168
|
status=non_default_status,
|
|
2690
3169
|
),
|
|
2691
3170
|
Interface.objects.create(
|
|
2692
3171
|
device=cls.device,
|
|
2693
|
-
name="Interface 3",
|
|
3172
|
+
name="Test Interface 3",
|
|
2694
3173
|
type=InterfaceTypeChoices.TYPE_BRIDGE,
|
|
2695
3174
|
status=non_default_status,
|
|
2696
3175
|
),
|
|
@@ -2878,6 +3357,10 @@ class ControllerManagedDeviceGroupTestCase(APIViewTestCases.APIViewTestCase):
|
|
|
2878
3357
|
"weight": 200,
|
|
2879
3358
|
},
|
|
2880
3359
|
]
|
|
3360
|
+
# changing controller is error-prone since a child group must have the same controller as its parent
|
|
3361
|
+
cls.update_data = {
|
|
3362
|
+
"weight": 300,
|
|
3363
|
+
}
|
|
2881
3364
|
cls.bulk_update_data = {
|
|
2882
3365
|
"weight": 300,
|
|
2883
3366
|
}
|