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