nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +111 -0
- nautobot/cloud/filters.py +184 -0
- nautobot/cloud/forms.py +333 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +247 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +173 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +113 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +96 -28
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +34 -4
- nautobot/core/jobs/__init__.py +17 -6
- nautobot/core/jobs/cleanup.py +100 -0
- nautobot/core/jobs/groups.py +38 -0
- nautobot/core/management/commands/generate_test_data.py +116 -3
- nautobot/core/models/__init__.py +34 -9
- nautobot/core/models/generics.py +19 -3
- nautobot/core/models/name_color_content_types.py +7 -28
- nautobot/core/models/querysets.py +4 -3
- nautobot/core/models/tree_queries.py +1 -1
- nautobot/core/models/utils.py +21 -5
- nautobot/core/settings.py +4 -30
- nautobot/core/settings.yaml +34 -27
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +127 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +13 -23
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +51 -13
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
- nautobot/core/tests/integration/test_swagger.py +1 -1
- nautobot/core/tests/nautobot_config.py +0 -1
- nautobot/core/tests/runner.py +2 -2
- nautobot/core/tests/test_api.py +1 -0
- nautobot/core/tests/test_authentication.py +7 -2
- nautobot/core/tests/test_filters.py +11 -9
- nautobot/core/tests/test_forms.py +9 -0
- nautobot/core/tests/test_graphql.py +27 -16
- nautobot/core/tests/test_jobs.py +123 -74
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +118 -66
- nautobot/core/views/mixins.py +104 -35
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +79 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +249 -18
- nautobot/dcim/filters/__init__.py +369 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +817 -109
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +861 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +275 -106
- nautobot/dcim/models/devices.py +466 -13
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +231 -59
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +15 -7
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +691 -208
- nautobot/dcim/tests/test_filters.py +836 -217
- nautobot/dcim/tests/test_models.py +1072 -39
- nautobot/dcim/tests/test_views.py +1488 -358
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1107 -120
- nautobot/extras/__init__.py +0 -1
- nautobot/extras/api/serializers.py +115 -3
- nautobot/extras/api/urls.py +12 -0
- nautobot/extras/api/views.py +125 -7
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +422 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +17 -4
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +16 -13
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
- nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
- nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
- nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
- nautobot/extras/migrations/0111_metadata.py +162 -0
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
- nautobot/extras/migrations/0113_saved_views.py +77 -0
- nautobot/extras/models/__init__.py +15 -1
- nautobot/extras/models/change_logging.py +3 -3
- nautobot/extras/models/contacts.py +4 -0
- nautobot/extras/models/customfields.py +18 -3
- nautobot/extras/models/groups.py +389 -225
- nautobot/extras/models/jobs.py +4 -84
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +116 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +96 -114
- nautobot/extras/tables.py +171 -47
- nautobot/extras/templates/extras/dynamicgroup.html +44 -15
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
- nautobot/extras/templates/extras/job.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +0 -11
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +501 -22
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +4 -4
- nautobot/extras/tests/test_models.py +499 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +565 -28
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +56 -45
- nautobot/extras/views.py +585 -96
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +14 -1
- nautobot/ipam/forms.py +9 -5
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +23 -9
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +1 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +8 -3
- nautobot/ipam/tests/test_querysets.py +1 -1
- nautobot/ipam/tests/test_views.py +3 -2
- nautobot/ipam/urls.py +2 -2
- nautobot/ipam/views.py +25 -28
- nautobot/project-static/css/base.css +20 -1
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +884 -80
- nautobot/project-static/docs/apps/index.html +884 -80
- nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
- nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
- nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
- nautobot/project-static/docs/development/apps/api/setup.html +884 -80
- nautobot/project-static/docs/development/apps/api/testing.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
- nautobot/project-static/docs/development/apps/index.html +884 -80
- nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
- nautobot/project-static/docs/development/core/application-registry.html +884 -80
- nautobot/project-static/docs/development/core/best-practices.html +885 -80
- nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
- nautobot/project-static/docs/development/core/caching.html +884 -80
- nautobot/project-static/docs/development/core/controllers.html +884 -80
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
- nautobot/project-static/docs/development/core/generic-views.html +884 -80
- nautobot/project-static/docs/development/core/getting-started.html +884 -80
- nautobot/project-static/docs/development/core/homepage.html +884 -80
- nautobot/project-static/docs/development/core/index.html +884 -91
- nautobot/project-static/docs/development/core/model-checklist.html +887 -81
- nautobot/project-static/docs/development/core/model-features.html +884 -80
- nautobot/project-static/docs/development/core/natural-keys.html +884 -80
- nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
- nautobot/project-static/docs/development/core/release-checklist.html +887 -83
- nautobot/project-static/docs/development/core/role-internals.html +884 -80
- nautobot/project-static/docs/development/core/settings.html +884 -80
- nautobot/project-static/docs/development/core/style-guide.html +885 -81
- nautobot/project-static/docs/development/core/templates.html +896 -81
- nautobot/project-static/docs/development/core/testing.html +884 -80
- nautobot/project-static/docs/development/core/user-preferences.html +884 -80
- nautobot/project-static/docs/development/index.html +884 -80
- nautobot/project-static/docs/development/jobs/index.html +1247 -457
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
- nautobot/project-static/docs/index.html +13 -8228
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
- nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
- nautobot/project-static/docs/models/dcim/module.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
- nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
- nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
- nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
- nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
- nautobot/project-static/docs/models/extras/role.html +15 -0
- nautobot/project-static/docs/models/extras/savedview.html +15 -0
- nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
- nautobot/project-static/docs/models/extras/status.html +15 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +892 -81
- nautobot/project-static/docs/overview/design_philosophy.html +886 -82
- nautobot/project-static/docs/overview/index.html +9032 -13
- nautobot/project-static/docs/release-notes/index.html +887 -83
- nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
- nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
- nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
- nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
- nautobot/project-static/docs/requirements.txt +4 -4
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +335 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
- nautobot/project-static/docs/user-guide/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
- nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +22 -18
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/tables.py +15 -5
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
nautobot/dcim/views.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from collections import OrderedDict
|
|
2
|
+
from copy import deepcopy
|
|
2
3
|
import logging
|
|
4
|
+
import re
|
|
3
5
|
import uuid
|
|
4
6
|
|
|
5
7
|
from django.contrib import messages
|
|
6
8
|
from django.contrib.contenttypes.models import ContentType
|
|
7
9
|
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
|
8
10
|
from django.core.paginator import EmptyPage, PageNotAnInteger
|
|
9
|
-
from django.db import transaction
|
|
11
|
+
from django.db import IntegrityError, transaction
|
|
10
12
|
from django.db.models import F, Prefetch
|
|
11
13
|
from django.forms import (
|
|
12
14
|
modelformset_factory,
|
|
@@ -20,18 +22,31 @@ from django.utils.html import format_html
|
|
|
20
22
|
from django.utils.http import url_has_allowed_host_and_scheme
|
|
21
23
|
from django.views.generic import View
|
|
22
24
|
from django_tables2 import RequestConfig
|
|
25
|
+
from rest_framework.decorators import action
|
|
26
|
+
from rest_framework.exceptions import MethodNotAllowed
|
|
27
|
+
from rest_framework.response import Response
|
|
23
28
|
|
|
24
29
|
from nautobot.circuits.models import Circuit
|
|
25
|
-
from nautobot.
|
|
30
|
+
from nautobot.cloud.models import CloudAccount
|
|
31
|
+
from nautobot.cloud.tables import CloudAccountTable
|
|
32
|
+
from nautobot.core.exceptions import AbortTransaction
|
|
33
|
+
from nautobot.core.forms import BulkRenameForm, ConfirmationForm, ImportForm, restrict_form_fields
|
|
26
34
|
from nautobot.core.models.querysets import count_related
|
|
27
35
|
from nautobot.core.templatetags.helpers import has_perms
|
|
36
|
+
from nautobot.core.utils.lookup import get_form_for_model
|
|
28
37
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
29
38
|
from nautobot.core.utils.requests import normalize_querydict
|
|
30
39
|
from nautobot.core.views import generic
|
|
31
40
|
from nautobot.core.views.mixins import (
|
|
32
41
|
GetReturnURLMixin,
|
|
42
|
+
ObjectBulkDestroyViewMixin,
|
|
43
|
+
ObjectBulkUpdateViewMixin,
|
|
44
|
+
ObjectChangeLogViewMixin,
|
|
33
45
|
ObjectDestroyViewMixin,
|
|
46
|
+
ObjectDetailViewMixin,
|
|
34
47
|
ObjectEditViewMixin,
|
|
48
|
+
ObjectListViewMixin,
|
|
49
|
+
ObjectNotesViewMixin,
|
|
35
50
|
ObjectPermissionRequiredMixin,
|
|
36
51
|
)
|
|
37
52
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
@@ -74,6 +89,10 @@ from .models import (
|
|
|
74
89
|
Location,
|
|
75
90
|
LocationType,
|
|
76
91
|
Manufacturer,
|
|
92
|
+
Module,
|
|
93
|
+
ModuleBay,
|
|
94
|
+
ModuleBayTemplate,
|
|
95
|
+
ModuleType,
|
|
77
96
|
PathEndpoint,
|
|
78
97
|
Platform,
|
|
79
98
|
PowerFeed,
|
|
@@ -158,7 +177,19 @@ class BaseDeviceComponentsBulkRenameView(generic.BulkRenameView):
|
|
|
158
177
|
def get_selected_objects_parents_name(self, selected_objects):
|
|
159
178
|
selected_object = selected_objects.first()
|
|
160
179
|
if selected_object and selected_object.device:
|
|
161
|
-
return selected_object.device.
|
|
180
|
+
return selected_object.device.display
|
|
181
|
+
if selected_object and selected_object.module:
|
|
182
|
+
return selected_object.module.display
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class BaseDeviceComponentTemplatesBulkRenameView(generic.BulkRenameView):
|
|
187
|
+
def get_selected_objects_parents_name(self, selected_objects):
|
|
188
|
+
selected_object = selected_objects.first()
|
|
189
|
+
if selected_object and selected_object.device_type:
|
|
190
|
+
return selected_object.device_type.display
|
|
191
|
+
if selected_object and selected_object.module_type:
|
|
192
|
+
return selected_object.module_type.display
|
|
162
193
|
return ""
|
|
163
194
|
|
|
164
195
|
|
|
@@ -168,7 +199,7 @@ class BaseDeviceComponentsBulkRenameView(generic.BulkRenameView):
|
|
|
168
199
|
|
|
169
200
|
|
|
170
201
|
class LocationTypeListView(generic.ObjectListView):
|
|
171
|
-
queryset = LocationType.objects.
|
|
202
|
+
queryset = LocationType.objects.all()
|
|
172
203
|
filterset = filters.LocationTypeFilterSet
|
|
173
204
|
filterset_form = forms.LocationTypeFilterForm
|
|
174
205
|
table = tables.LocationTypeTable
|
|
@@ -370,7 +401,7 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
|
370
401
|
|
|
371
402
|
associated_object_id = obj.pk
|
|
372
403
|
associated_object_content_type = ContentType.objects.get_for_model(Location)
|
|
373
|
-
|
|
404
|
+
migrate_action = request.POST.get("action")
|
|
374
405
|
try:
|
|
375
406
|
with transaction.atomic():
|
|
376
407
|
if not has_perms(request.user, ["extras.add_contactassociation"]):
|
|
@@ -379,7 +410,7 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
|
379
410
|
)
|
|
380
411
|
contact = None
|
|
381
412
|
team = None
|
|
382
|
-
if
|
|
413
|
+
if migrate_action == LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_CONTACT:
|
|
383
414
|
if not has_perms(request.user, ["extras.add_contact"]):
|
|
384
415
|
raise PermissionDenied("ObjectPermission extras.add_contact is needed to perform this action")
|
|
385
416
|
contact = Contact(
|
|
@@ -390,7 +421,7 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
|
390
421
|
contact.validated_save()
|
|
391
422
|
# Trigger permission check
|
|
392
423
|
Contact.objects.restrict(request.user, "view").get(pk=contact.pk)
|
|
393
|
-
elif
|
|
424
|
+
elif migrate_action == LocationDataToContactActionChoices.CREATE_AND_ASSIGN_NEW_TEAM:
|
|
394
425
|
if not has_perms(request.user, ["extras.add_team"]):
|
|
395
426
|
raise PermissionDenied("ObjectPermission extras.add_team is needed to perform this action")
|
|
396
427
|
team = Team(
|
|
@@ -401,12 +432,12 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
|
401
432
|
team.validated_save()
|
|
402
433
|
# Trigger permission check
|
|
403
434
|
Team.objects.restrict(request.user, "view").get(pk=team.pk)
|
|
404
|
-
elif
|
|
435
|
+
elif migrate_action == LocationDataToContactActionChoices.ASSOCIATE_EXISTING_CONTACT:
|
|
405
436
|
contact = Contact.objects.restrict(request.user, "view").get(pk=request.POST.get("contact"))
|
|
406
|
-
elif
|
|
437
|
+
elif migrate_action == LocationDataToContactActionChoices.ASSOCIATE_EXISTING_TEAM:
|
|
407
438
|
team = Team.objects.restrict(request.user, "view").get(pk=request.POST.get("team"))
|
|
408
439
|
else:
|
|
409
|
-
raise ValueError(f"Invalid action {
|
|
440
|
+
raise ValueError(f"Invalid action {migrate_action} passed from the form")
|
|
410
441
|
|
|
411
442
|
association = ContactAssociation(
|
|
412
443
|
contact=contact,
|
|
@@ -470,7 +501,7 @@ class MigrateLocationDataToContactView(generic.ObjectEditView):
|
|
|
470
501
|
|
|
471
502
|
|
|
472
503
|
class RackGroupListView(generic.ObjectListView):
|
|
473
|
-
queryset = RackGroup.objects.
|
|
504
|
+
queryset = RackGroup.objects.all()
|
|
474
505
|
filterset = filters.RackGroupFilterSet
|
|
475
506
|
filterset_form = forms.RackGroupFilterForm
|
|
476
507
|
table = tables.RackGroupTable
|
|
@@ -514,7 +545,7 @@ class RackGroupBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unus
|
|
|
514
545
|
|
|
515
546
|
|
|
516
547
|
class RackGroupBulkDeleteView(generic.BulkDeleteView):
|
|
517
|
-
queryset = RackGroup.objects.
|
|
548
|
+
queryset = RackGroup.objects.all()
|
|
518
549
|
filterset = filters.RackGroupFilterSet
|
|
519
550
|
table = tables.RackGroupTable
|
|
520
551
|
|
|
@@ -525,7 +556,7 @@ class RackGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
525
556
|
|
|
526
557
|
|
|
527
558
|
class RackListView(generic.ObjectListView):
|
|
528
|
-
queryset = Rack.objects.
|
|
559
|
+
queryset = Rack.objects.all()
|
|
529
560
|
filterset = filters.RackFilterSet
|
|
530
561
|
filterset_form = forms.RackFilterForm
|
|
531
562
|
table = tables.RackDetailTable
|
|
@@ -635,14 +666,14 @@ class RackBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, unused
|
|
|
635
666
|
|
|
636
667
|
|
|
637
668
|
class RackBulkEditView(generic.BulkEditView):
|
|
638
|
-
queryset = Rack.objects.
|
|
669
|
+
queryset = Rack.objects.all()
|
|
639
670
|
filterset = filters.RackFilterSet
|
|
640
671
|
table = tables.RackTable
|
|
641
672
|
form = forms.RackBulkEditForm
|
|
642
673
|
|
|
643
674
|
|
|
644
675
|
class RackBulkDeleteView(generic.BulkDeleteView):
|
|
645
|
-
queryset = Rack.objects.
|
|
676
|
+
queryset = Rack.objects.all()
|
|
646
677
|
filterset = filters.RackFilterSet
|
|
647
678
|
table = tables.RackTable
|
|
648
679
|
|
|
@@ -704,11 +735,7 @@ class RackReservationBulkDeleteView(generic.BulkDeleteView):
|
|
|
704
735
|
|
|
705
736
|
|
|
706
737
|
class ManufacturerListView(generic.ObjectListView):
|
|
707
|
-
queryset = Manufacturer.objects.
|
|
708
|
-
device_type_count=count_related(DeviceType, "manufacturer"),
|
|
709
|
-
inventory_item_count=count_related(InventoryItem, "manufacturer"),
|
|
710
|
-
platform_count=count_related(Platform, "manufacturer"),
|
|
711
|
-
)
|
|
738
|
+
queryset = Manufacturer.objects.all()
|
|
712
739
|
filterset = filters.ManufacturerFilterSet
|
|
713
740
|
filterset_form = forms.ManufacturerFilterForm
|
|
714
741
|
table = tables.ManufacturerTable
|
|
@@ -733,7 +760,25 @@ class ManufacturerView(generic.ObjectView):
|
|
|
733
760
|
}
|
|
734
761
|
RequestConfig(request, paginate).configure(device_table)
|
|
735
762
|
|
|
736
|
-
|
|
763
|
+
# Cloud Accounts
|
|
764
|
+
cloud_accounts = (
|
|
765
|
+
CloudAccount.objects.restrict(request.user, "view")
|
|
766
|
+
.filter(provider=instance)
|
|
767
|
+
.select_related("secrets_group")
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
cloud_account_table = CloudAccountTable(cloud_accounts)
|
|
771
|
+
paginate = {
|
|
772
|
+
"paginator_class": EnhancedPaginator,
|
|
773
|
+
"per_page": get_paginate_count(request),
|
|
774
|
+
}
|
|
775
|
+
RequestConfig(request, paginate).configure(cloud_account_table)
|
|
776
|
+
|
|
777
|
+
return {
|
|
778
|
+
"device_table": device_table,
|
|
779
|
+
"cloud_account_table": cloud_account_table,
|
|
780
|
+
**super().get_extra_context(request, instance),
|
|
781
|
+
}
|
|
737
782
|
|
|
738
783
|
|
|
739
784
|
class ManufacturerEditView(generic.ObjectEditView):
|
|
@@ -751,7 +796,7 @@ class ManufacturerBulkImportView(generic.BulkImportView): # 3.0 TODO: remove, u
|
|
|
751
796
|
|
|
752
797
|
|
|
753
798
|
class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
|
754
|
-
queryset = Manufacturer.objects.
|
|
799
|
+
queryset = Manufacturer.objects.all()
|
|
755
800
|
table = tables.ManufacturerTable
|
|
756
801
|
filterset = filters.ManufacturerFilterSet
|
|
757
802
|
|
|
@@ -762,7 +807,7 @@ class ManufacturerBulkDeleteView(generic.BulkDeleteView):
|
|
|
762
807
|
|
|
763
808
|
|
|
764
809
|
class DeviceTypeListView(generic.ObjectListView):
|
|
765
|
-
queryset = DeviceType.objects.
|
|
810
|
+
queryset = DeviceType.objects.all()
|
|
766
811
|
filterset = filters.DeviceTypeFilterSet
|
|
767
812
|
filterset_form = forms.DeviceTypeFilterForm
|
|
768
813
|
table = tables.DeviceTypeTable
|
|
@@ -810,6 +855,10 @@ class DeviceTypeView(generic.ObjectView):
|
|
|
810
855
|
DeviceBayTemplate.objects.restrict(request.user, "view").filter(device_type=instance),
|
|
811
856
|
orderable=False,
|
|
812
857
|
)
|
|
858
|
+
modulebay_table = tables.ModuleBayTemplateTable(
|
|
859
|
+
ModuleBayTemplate.objects.restrict(request.user, "view").filter(device_type=instance),
|
|
860
|
+
orderable=False,
|
|
861
|
+
)
|
|
813
862
|
if request.user.has_perm("dcim.change_devicetype"):
|
|
814
863
|
consoleport_table.columns.show("pk")
|
|
815
864
|
consoleserverport_table.columns.show("pk")
|
|
@@ -819,6 +868,7 @@ class DeviceTypeView(generic.ObjectView):
|
|
|
819
868
|
front_port_table.columns.show("pk")
|
|
820
869
|
rear_port_table.columns.show("pk")
|
|
821
870
|
devicebay_table.columns.show("pk")
|
|
871
|
+
modulebay_table.columns.show("pk")
|
|
822
872
|
|
|
823
873
|
software_image_files_table = tables.SoftwareImageFileTable(
|
|
824
874
|
instance.software_image_files.restrict(request.user, "view").annotate(
|
|
@@ -838,6 +888,7 @@ class DeviceTypeView(generic.ObjectView):
|
|
|
838
888
|
"front_port_table": front_port_table,
|
|
839
889
|
"rear_port_table": rear_port_table,
|
|
840
890
|
"devicebay_table": devicebay_table,
|
|
891
|
+
"modulebay_table": modulebay_table,
|
|
841
892
|
"software_image_files_table": software_image_files_table,
|
|
842
893
|
**super().get_extra_context(request, instance),
|
|
843
894
|
}
|
|
@@ -864,6 +915,7 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
|
|
864
915
|
"dcim.add_frontporttemplate",
|
|
865
916
|
"dcim.add_rearporttemplate",
|
|
866
917
|
"dcim.add_devicebaytemplate",
|
|
918
|
+
"dcim.add_modulebaytemplate",
|
|
867
919
|
]
|
|
868
920
|
queryset = DeviceType.objects.all()
|
|
869
921
|
model_form = forms.DeviceTypeImportForm
|
|
@@ -877,31 +929,255 @@ class DeviceTypeImportView(generic.ObjectImportView):
|
|
|
877
929
|
("rear-ports", forms.RearPortTemplateImportForm),
|
|
878
930
|
("front-ports", forms.FrontPortTemplateImportForm),
|
|
879
931
|
("device-bays", forms.DeviceBayTemplateImportForm),
|
|
932
|
+
("module-bays", forms.ModuleBayTemplateImportForm),
|
|
880
933
|
)
|
|
881
934
|
)
|
|
882
935
|
|
|
883
936
|
|
|
884
937
|
class DeviceTypeBulkEditView(generic.BulkEditView):
|
|
885
|
-
queryset = (
|
|
886
|
-
DeviceType.objects.select_related("manufacturer")
|
|
887
|
-
.prefetch_related("software_image_files")
|
|
888
|
-
.annotate(device_count=count_related(Device, "device_type"))
|
|
889
|
-
)
|
|
938
|
+
queryset = DeviceType.objects.all()
|
|
890
939
|
filterset = filters.DeviceTypeFilterSet
|
|
891
940
|
table = tables.DeviceTypeTable
|
|
892
941
|
form = forms.DeviceTypeBulkEditForm
|
|
893
942
|
|
|
894
943
|
|
|
895
944
|
class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
|
896
|
-
queryset = (
|
|
897
|
-
DeviceType.objects.select_related("manufacturer")
|
|
898
|
-
.prefetch_related("software_image_files")
|
|
899
|
-
.annotate(device_count=count_related(Device, "device_type"))
|
|
900
|
-
)
|
|
945
|
+
queryset = DeviceType.objects.all()
|
|
901
946
|
filterset = filters.DeviceTypeFilterSet
|
|
902
947
|
table = tables.DeviceTypeTable
|
|
903
948
|
|
|
904
949
|
|
|
950
|
+
#
|
|
951
|
+
# Module types
|
|
952
|
+
#
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
class ModuleTypeUIViewSet(
|
|
956
|
+
ObjectDetailViewMixin,
|
|
957
|
+
ObjectListViewMixin,
|
|
958
|
+
ObjectEditViewMixin,
|
|
959
|
+
ObjectDestroyViewMixin,
|
|
960
|
+
ObjectBulkDestroyViewMixin,
|
|
961
|
+
ObjectBulkUpdateViewMixin,
|
|
962
|
+
ObjectChangeLogViewMixin,
|
|
963
|
+
ObjectNotesViewMixin,
|
|
964
|
+
):
|
|
965
|
+
queryset = ModuleType.objects.all()
|
|
966
|
+
filterset_class = filters.ModuleTypeFilterSet
|
|
967
|
+
filterset_form_class = forms.ModuleTypeFilterForm
|
|
968
|
+
form_class = forms.ModuleTypeForm
|
|
969
|
+
import_model_form = forms.ModuleTypeImportForm
|
|
970
|
+
bulk_update_form_class = forms.ModuleTypeBulkEditForm
|
|
971
|
+
serializer_class = serializers.ModuleTypeSerializer
|
|
972
|
+
table_class = tables.ModuleTypeTable
|
|
973
|
+
related_object_forms = {
|
|
974
|
+
"console-ports": forms.ConsolePortTemplateImportForm,
|
|
975
|
+
"console-server-ports": forms.ConsoleServerPortTemplateImportForm,
|
|
976
|
+
"power-ports": forms.PowerPortTemplateImportForm,
|
|
977
|
+
"power-outlets": forms.PowerOutletTemplateImportForm,
|
|
978
|
+
"interfaces": forms.InterfaceTemplateImportForm,
|
|
979
|
+
"rear-ports": forms.RearPortTemplateImportForm,
|
|
980
|
+
"front-ports": forms.FrontPortTemplateImportForm,
|
|
981
|
+
"module-bays": forms.ModuleBayTemplateImportForm,
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
def get_required_permission(self):
|
|
985
|
+
view_action = self.get_action()
|
|
986
|
+
if view_action == "import_view":
|
|
987
|
+
return [
|
|
988
|
+
*self.get_permissions_for_model(ModuleType, ["add"]),
|
|
989
|
+
*self.get_permissions_for_model(ConsolePortTemplate, ["add"]),
|
|
990
|
+
*self.get_permissions_for_model(ConsoleServerPortTemplate, ["add"]),
|
|
991
|
+
*self.get_permissions_for_model(PowerPortTemplate, ["add"]),
|
|
992
|
+
*self.get_permissions_for_model(PowerOutletTemplate, ["add"]),
|
|
993
|
+
*self.get_permissions_for_model(InterfaceTemplate, ["add"]),
|
|
994
|
+
*self.get_permissions_for_model(FrontPortTemplate, ["add"]),
|
|
995
|
+
*self.get_permissions_for_model(RearPortTemplate, ["add"]),
|
|
996
|
+
*self.get_permissions_for_model(ModuleBayTemplate, ["add"]),
|
|
997
|
+
]
|
|
998
|
+
|
|
999
|
+
return super().get_required_permission()
|
|
1000
|
+
|
|
1001
|
+
def get_extra_context(self, request, instance):
|
|
1002
|
+
if not instance:
|
|
1003
|
+
return {}
|
|
1004
|
+
|
|
1005
|
+
instance_count = Module.objects.restrict(request.user).filter(module_type=instance).count()
|
|
1006
|
+
|
|
1007
|
+
# Component tables
|
|
1008
|
+
consoleport_table = tables.ConsolePortTemplateTable(
|
|
1009
|
+
ConsolePortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1010
|
+
orderable=False,
|
|
1011
|
+
)
|
|
1012
|
+
consoleserverport_table = tables.ConsoleServerPortTemplateTable(
|
|
1013
|
+
ConsoleServerPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1014
|
+
orderable=False,
|
|
1015
|
+
)
|
|
1016
|
+
powerport_table = tables.PowerPortTemplateTable(
|
|
1017
|
+
PowerPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1018
|
+
orderable=False,
|
|
1019
|
+
)
|
|
1020
|
+
poweroutlet_table = tables.PowerOutletTemplateTable(
|
|
1021
|
+
PowerOutletTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1022
|
+
orderable=False,
|
|
1023
|
+
)
|
|
1024
|
+
interface_table = tables.InterfaceTemplateTable(
|
|
1025
|
+
list(InterfaceTemplate.objects.restrict(request.user, "view").filter(module_type=instance)),
|
|
1026
|
+
orderable=False,
|
|
1027
|
+
)
|
|
1028
|
+
front_port_table = tables.FrontPortTemplateTable(
|
|
1029
|
+
FrontPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1030
|
+
orderable=False,
|
|
1031
|
+
)
|
|
1032
|
+
rear_port_table = tables.RearPortTemplateTable(
|
|
1033
|
+
RearPortTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1034
|
+
orderable=False,
|
|
1035
|
+
)
|
|
1036
|
+
modulebay_table = tables.ModuleBayTemplateTable(
|
|
1037
|
+
ModuleBayTemplate.objects.restrict(request.user, "view").filter(module_type=instance),
|
|
1038
|
+
orderable=False,
|
|
1039
|
+
)
|
|
1040
|
+
if request.user.has_perm("dcim.change_moduletype"):
|
|
1041
|
+
consoleport_table.columns.show("pk")
|
|
1042
|
+
consoleserverport_table.columns.show("pk")
|
|
1043
|
+
powerport_table.columns.show("pk")
|
|
1044
|
+
poweroutlet_table.columns.show("pk")
|
|
1045
|
+
interface_table.columns.show("pk")
|
|
1046
|
+
front_port_table.columns.show("pk")
|
|
1047
|
+
rear_port_table.columns.show("pk")
|
|
1048
|
+
modulebay_table.columns.show("pk")
|
|
1049
|
+
|
|
1050
|
+
return {
|
|
1051
|
+
"instance_count": instance_count,
|
|
1052
|
+
"consoleport_table": consoleport_table,
|
|
1053
|
+
"consoleserverport_table": consoleserverport_table,
|
|
1054
|
+
"powerport_table": powerport_table,
|
|
1055
|
+
"poweroutlet_table": poweroutlet_table,
|
|
1056
|
+
"interface_table": interface_table,
|
|
1057
|
+
"front_port_table": front_port_table,
|
|
1058
|
+
"rear_port_table": rear_port_table,
|
|
1059
|
+
"modulebay_table": modulebay_table,
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
@action(
|
|
1063
|
+
detail=False,
|
|
1064
|
+
methods=["GET", "POST"],
|
|
1065
|
+
url_name="import",
|
|
1066
|
+
url_path="import",
|
|
1067
|
+
)
|
|
1068
|
+
def import_view(self, request, *args, **kwargs):
|
|
1069
|
+
if request.method == "POST":
|
|
1070
|
+
form = ImportForm(request.POST)
|
|
1071
|
+
|
|
1072
|
+
if form.is_valid():
|
|
1073
|
+
self.logger.debug("Import form validation was successful")
|
|
1074
|
+
|
|
1075
|
+
# Initialize model form
|
|
1076
|
+
data = form.cleaned_data["data"]
|
|
1077
|
+
model_form = self.import_model_form(data)
|
|
1078
|
+
restrict_form_fields(model_form, request.user)
|
|
1079
|
+
|
|
1080
|
+
# Assign default values for any fields which were not specified. We have to do this manually because passing
|
|
1081
|
+
# 'initial=' to the form on initialization merely sets default values for the widgets. Since widgets are not
|
|
1082
|
+
# used for YAML/JSON import, we first bind the imported data normally, then update the form's data with the
|
|
1083
|
+
# applicable field defaults as needed prior to form validation.
|
|
1084
|
+
for field_name, field in model_form.fields.items():
|
|
1085
|
+
if field_name not in data and hasattr(field, "initial"):
|
|
1086
|
+
model_form.data[field_name] = field.initial
|
|
1087
|
+
|
|
1088
|
+
if model_form.is_valid():
|
|
1089
|
+
try:
|
|
1090
|
+
with transaction.atomic():
|
|
1091
|
+
# Save the primary object
|
|
1092
|
+
obj = model_form.save()
|
|
1093
|
+
|
|
1094
|
+
# Enforce object-level permissions
|
|
1095
|
+
self.queryset.get(pk=obj.pk)
|
|
1096
|
+
|
|
1097
|
+
self.logger.debug(f"Created {obj} (PK: {obj.pk})")
|
|
1098
|
+
|
|
1099
|
+
# Iterate through the related object forms (if any), validating and saving each instance.
|
|
1100
|
+
for (
|
|
1101
|
+
field_name,
|
|
1102
|
+
related_object_form,
|
|
1103
|
+
) in self.related_object_forms.items():
|
|
1104
|
+
self.logger.debug(f"Processing form for related objects: {related_object_form}")
|
|
1105
|
+
|
|
1106
|
+
related_obj_pks = []
|
|
1107
|
+
for i, rel_obj_data in enumerate(data.get(field_name, [])):
|
|
1108
|
+
# add parent object key to related object data
|
|
1109
|
+
rel_obj_data[obj._meta.verbose_name.replace(" ", "_")] = str(obj.pk)
|
|
1110
|
+
f = related_object_form(rel_obj_data)
|
|
1111
|
+
|
|
1112
|
+
for subfield_name, field in f.fields.items():
|
|
1113
|
+
if subfield_name not in rel_obj_data and hasattr(field, "initial"):
|
|
1114
|
+
f.data[subfield_name] = field.initial
|
|
1115
|
+
|
|
1116
|
+
if f.is_valid():
|
|
1117
|
+
related_obj = f.save()
|
|
1118
|
+
related_obj_pks.append(related_obj.pk)
|
|
1119
|
+
else:
|
|
1120
|
+
# Replicate errors on the related object form to the primary form for display
|
|
1121
|
+
for subfield_name, errors in f.errors.items():
|
|
1122
|
+
for err in errors:
|
|
1123
|
+
err_msg = f"{field_name}[{i}] {subfield_name}: {err}"
|
|
1124
|
+
model_form.add_error(None, err_msg)
|
|
1125
|
+
raise AbortTransaction()
|
|
1126
|
+
|
|
1127
|
+
# Enforce object-level permissions on related objects
|
|
1128
|
+
model = related_object_form.Meta.model
|
|
1129
|
+
if model.objects.filter(pk__in=related_obj_pks).count() != len(related_obj_pks):
|
|
1130
|
+
raise ObjectDoesNotExist
|
|
1131
|
+
|
|
1132
|
+
except AbortTransaction:
|
|
1133
|
+
pass
|
|
1134
|
+
|
|
1135
|
+
except ObjectDoesNotExist:
|
|
1136
|
+
msg = "Object creation failed due to object-level permissions violation"
|
|
1137
|
+
self.logger.debug(msg)
|
|
1138
|
+
model_form.add_error(None, msg)
|
|
1139
|
+
|
|
1140
|
+
if not model_form.errors:
|
|
1141
|
+
self.logger.info(f"Import object {obj} (PK: {obj.pk})")
|
|
1142
|
+
messages.success(
|
|
1143
|
+
request,
|
|
1144
|
+
format_html('Imported object: <a href="{}">{}</a>', obj.get_absolute_url(), obj),
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
if "_addanother" in request.POST:
|
|
1148
|
+
return redirect(request.get_full_path())
|
|
1149
|
+
|
|
1150
|
+
return_url = form.cleaned_data.get("return_url")
|
|
1151
|
+
if url_has_allowed_host_and_scheme(url=return_url, allowed_hosts=request.get_host()):
|
|
1152
|
+
return redirect(iri_to_uri(return_url))
|
|
1153
|
+
else:
|
|
1154
|
+
return redirect(self.get_return_url(request, obj))
|
|
1155
|
+
|
|
1156
|
+
else:
|
|
1157
|
+
self.logger.debug("Model form validation failed")
|
|
1158
|
+
|
|
1159
|
+
# Replicate model form errors for display
|
|
1160
|
+
for field, errors in model_form.errors.items():
|
|
1161
|
+
for err in errors:
|
|
1162
|
+
if field == "__all__":
|
|
1163
|
+
form.add_error(None, err)
|
|
1164
|
+
else:
|
|
1165
|
+
form.add_error(None, f"{field}: {err}")
|
|
1166
|
+
|
|
1167
|
+
else:
|
|
1168
|
+
self.logger.debug("Import form validation failed")
|
|
1169
|
+
|
|
1170
|
+
else:
|
|
1171
|
+
form = ImportForm()
|
|
1172
|
+
|
|
1173
|
+
return Response(
|
|
1174
|
+
{
|
|
1175
|
+
"template": "generic/object_import.html",
|
|
1176
|
+
"form": form,
|
|
1177
|
+
}
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
|
|
905
1181
|
#
|
|
906
1182
|
# Console port templates
|
|
907
1183
|
#
|
|
@@ -930,7 +1206,7 @@ class ConsolePortTemplateBulkEditView(generic.BulkEditView):
|
|
|
930
1206
|
filterset = filters.ConsolePortTemplateFilterSet
|
|
931
1207
|
|
|
932
1208
|
|
|
933
|
-
class ConsolePortTemplateBulkRenameView(
|
|
1209
|
+
class ConsolePortTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
934
1210
|
queryset = ConsolePortTemplate.objects.all()
|
|
935
1211
|
|
|
936
1212
|
|
|
@@ -968,7 +1244,7 @@ class ConsoleServerPortTemplateBulkEditView(generic.BulkEditView):
|
|
|
968
1244
|
filterset = filters.ConsoleServerPortTemplateFilterSet
|
|
969
1245
|
|
|
970
1246
|
|
|
971
|
-
class ConsoleServerPortTemplateBulkRenameView(
|
|
1247
|
+
class ConsoleServerPortTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
972
1248
|
queryset = ConsoleServerPortTemplate.objects.all()
|
|
973
1249
|
|
|
974
1250
|
|
|
@@ -1006,7 +1282,7 @@ class PowerPortTemplateBulkEditView(generic.BulkEditView):
|
|
|
1006
1282
|
filterset = filters.PowerPortTemplateFilterSet
|
|
1007
1283
|
|
|
1008
1284
|
|
|
1009
|
-
class PowerPortTemplateBulkRenameView(
|
|
1285
|
+
class PowerPortTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1010
1286
|
queryset = PowerPortTemplate.objects.all()
|
|
1011
1287
|
|
|
1012
1288
|
|
|
@@ -1044,7 +1320,7 @@ class PowerOutletTemplateBulkEditView(generic.BulkEditView):
|
|
|
1044
1320
|
filterset = filters.PowerOutletTemplateFilterSet
|
|
1045
1321
|
|
|
1046
1322
|
|
|
1047
|
-
class PowerOutletTemplateBulkRenameView(
|
|
1323
|
+
class PowerOutletTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1048
1324
|
queryset = PowerOutletTemplate.objects.all()
|
|
1049
1325
|
|
|
1050
1326
|
|
|
@@ -1081,7 +1357,7 @@ class InterfaceTemplateBulkEditView(generic.BulkEditView):
|
|
|
1081
1357
|
filterset = filters.InterfaceTemplateFilterSet
|
|
1082
1358
|
|
|
1083
1359
|
|
|
1084
|
-
class InterfaceTemplateBulkRenameView(
|
|
1360
|
+
class InterfaceTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1085
1361
|
queryset = InterfaceTemplate.objects.all()
|
|
1086
1362
|
|
|
1087
1363
|
|
|
@@ -1118,7 +1394,7 @@ class FrontPortTemplateBulkEditView(generic.BulkEditView):
|
|
|
1118
1394
|
filterset = filters.FrontPortTemplateFilterSet
|
|
1119
1395
|
|
|
1120
1396
|
|
|
1121
|
-
class FrontPortTemplateBulkRenameView(
|
|
1397
|
+
class FrontPortTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1122
1398
|
queryset = FrontPortTemplate.objects.all()
|
|
1123
1399
|
|
|
1124
1400
|
|
|
@@ -1155,7 +1431,7 @@ class RearPortTemplateBulkEditView(generic.BulkEditView):
|
|
|
1155
1431
|
filterset = filters.RearPortTemplateFilterSet
|
|
1156
1432
|
|
|
1157
1433
|
|
|
1158
|
-
class RearPortTemplateBulkRenameView(
|
|
1434
|
+
class RearPortTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1159
1435
|
queryset = RearPortTemplate.objects.all()
|
|
1160
1436
|
|
|
1161
1437
|
|
|
@@ -1192,7 +1468,7 @@ class DeviceBayTemplateBulkEditView(generic.BulkEditView):
|
|
|
1192
1468
|
filterset = filters.DeviceBayTemplateFilterSet
|
|
1193
1469
|
|
|
1194
1470
|
|
|
1195
|
-
class DeviceBayTemplateBulkRenameView(
|
|
1471
|
+
class DeviceBayTemplateBulkRenameView(BaseDeviceComponentTemplatesBulkRenameView):
|
|
1196
1472
|
queryset = DeviceBayTemplate.objects.all()
|
|
1197
1473
|
|
|
1198
1474
|
|
|
@@ -1202,16 +1478,212 @@ class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
|
|
1202
1478
|
filterset = filters.DeviceBayTemplateFilterSet
|
|
1203
1479
|
|
|
1204
1480
|
|
|
1481
|
+
#
|
|
1482
|
+
# Module bay templates
|
|
1483
|
+
#
|
|
1484
|
+
|
|
1485
|
+
|
|
1486
|
+
class ModuleBayCommonViewSetMixin:
|
|
1487
|
+
"""NautobotUIViewSet for ModuleBay views to handle templated create and bulk rename views."""
|
|
1488
|
+
|
|
1489
|
+
def create(self, request, *args, **kwargs):
|
|
1490
|
+
if request.method == "POST":
|
|
1491
|
+
return self.perform_create(request, *args, **kwargs)
|
|
1492
|
+
|
|
1493
|
+
form = self.create_form_class(initial=request.GET)
|
|
1494
|
+
model_form = self.model_form_class(request.GET)
|
|
1495
|
+
|
|
1496
|
+
return Response(
|
|
1497
|
+
{
|
|
1498
|
+
"template": self.create_template_name,
|
|
1499
|
+
"component_type": self.queryset.model._meta.verbose_name,
|
|
1500
|
+
"model_form": model_form,
|
|
1501
|
+
"form": form,
|
|
1502
|
+
"return_url": self.get_return_url(request),
|
|
1503
|
+
},
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
def perform_create(self, request, *args, **kwargs):
|
|
1507
|
+
form = self.create_form_class(
|
|
1508
|
+
request.POST,
|
|
1509
|
+
initial=normalize_querydict(request.GET, form_class=self.create_form_class),
|
|
1510
|
+
)
|
|
1511
|
+
model_form = self.model_form_class(
|
|
1512
|
+
request.POST,
|
|
1513
|
+
initial=normalize_querydict(request.GET, form_class=self.model_form_class),
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
if form.is_valid():
|
|
1517
|
+
new_components = []
|
|
1518
|
+
data = deepcopy(request.POST)
|
|
1519
|
+
|
|
1520
|
+
names = form.cleaned_data["name_pattern"]
|
|
1521
|
+
labels = form.cleaned_data.get("label_pattern")
|
|
1522
|
+
positions = form.cleaned_data.get("position_pattern")
|
|
1523
|
+
for i, name in enumerate(names):
|
|
1524
|
+
label = labels[i] if labels else None
|
|
1525
|
+
position = positions[i] if positions else None
|
|
1526
|
+
# Initialize the individual component form
|
|
1527
|
+
data["name"] = name
|
|
1528
|
+
data["label"] = label
|
|
1529
|
+
data["position"] = position
|
|
1530
|
+
component_form = self.model_form_class(
|
|
1531
|
+
data,
|
|
1532
|
+
initial=normalize_querydict(request.GET, form_class=self.model_form_class),
|
|
1533
|
+
)
|
|
1534
|
+
if component_form.is_valid():
|
|
1535
|
+
new_components.append(component_form)
|
|
1536
|
+
else:
|
|
1537
|
+
for field, errors in component_form.errors.as_data().items():
|
|
1538
|
+
# Assign errors on the child form's name/position/label field to *_pattern fields on the parent form
|
|
1539
|
+
if field.endswith("_pattern"):
|
|
1540
|
+
field = field[:-8]
|
|
1541
|
+
for e in errors:
|
|
1542
|
+
err_str = ", ".join(e)
|
|
1543
|
+
form.add_error(field, f"{name}: {err_str}")
|
|
1544
|
+
|
|
1545
|
+
if not form.errors:
|
|
1546
|
+
try:
|
|
1547
|
+
with transaction.atomic():
|
|
1548
|
+
# Create the new components
|
|
1549
|
+
new_objs = []
|
|
1550
|
+
for component_form in new_components:
|
|
1551
|
+
obj = component_form.save()
|
|
1552
|
+
new_objs.append(obj)
|
|
1553
|
+
|
|
1554
|
+
# Enforce object-level permissions
|
|
1555
|
+
if self.get_queryset().filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
|
|
1556
|
+
raise ObjectDoesNotExist
|
|
1557
|
+
|
|
1558
|
+
messages.success(
|
|
1559
|
+
request,
|
|
1560
|
+
f"Added {len(new_components)} {self.queryset.model._meta.verbose_name_plural}",
|
|
1561
|
+
)
|
|
1562
|
+
if "_addanother" in request.POST:
|
|
1563
|
+
return redirect(request.get_full_path())
|
|
1564
|
+
else:
|
|
1565
|
+
return redirect(self.get_return_url(request))
|
|
1566
|
+
|
|
1567
|
+
except ObjectDoesNotExist:
|
|
1568
|
+
msg = "Component creation failed due to object-level permissions violation"
|
|
1569
|
+
form.add_error(None, msg)
|
|
1570
|
+
|
|
1571
|
+
return Response(
|
|
1572
|
+
{
|
|
1573
|
+
"template": self.create_template_name,
|
|
1574
|
+
"component_type": self.queryset.model._meta.verbose_name,
|
|
1575
|
+
"form": form,
|
|
1576
|
+
"model_form": model_form,
|
|
1577
|
+
"return_url": self.get_return_url(request),
|
|
1578
|
+
},
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
def _bulk_rename(self, request, *args, **kwargs):
|
|
1582
|
+
# TODO: This shouldn't be needed but default behavior of custom actions that don't support "GET" is broken
|
|
1583
|
+
if request.method != "POST":
|
|
1584
|
+
raise MethodNotAllowed(request.method)
|
|
1585
|
+
|
|
1586
|
+
query_pks = request.POST.getlist("pk")
|
|
1587
|
+
selected_objects = self.get_queryset().filter(pk__in=query_pks) if query_pks else None
|
|
1588
|
+
|
|
1589
|
+
# Create a new Form class from BulkRenameForm
|
|
1590
|
+
class _Form(BulkRenameForm):
|
|
1591
|
+
pk = ModelMultipleChoiceField(queryset=self.get_queryset(), widget=MultipleHiddenInput())
|
|
1592
|
+
|
|
1593
|
+
# selected_objects would return False; if no query_pks or invalid query_pks
|
|
1594
|
+
if not selected_objects:
|
|
1595
|
+
messages.warning(request, f"No valid {self.queryset.model._meta.verbose_name_plural} were selected.")
|
|
1596
|
+
return redirect(self.get_return_url(request))
|
|
1597
|
+
|
|
1598
|
+
if "_preview" in request.POST or "_apply" in request.POST:
|
|
1599
|
+
form = _Form(request.POST, initial={"pk": query_pks})
|
|
1600
|
+
if form.is_valid():
|
|
1601
|
+
try:
|
|
1602
|
+
with transaction.atomic():
|
|
1603
|
+
renamed_pks = []
|
|
1604
|
+
for obj in selected_objects:
|
|
1605
|
+
find = form.cleaned_data["find"]
|
|
1606
|
+
replace = form.cleaned_data["replace"]
|
|
1607
|
+
if form.cleaned_data["use_regex"]:
|
|
1608
|
+
try:
|
|
1609
|
+
obj.new_name = re.sub(find, replace, obj.name)
|
|
1610
|
+
# Catch regex group reference errors
|
|
1611
|
+
except re.error:
|
|
1612
|
+
obj.new_name = obj.name
|
|
1613
|
+
else:
|
|
1614
|
+
obj.new_name = obj.name.replace(find, replace)
|
|
1615
|
+
renamed_pks.append(obj.pk)
|
|
1616
|
+
|
|
1617
|
+
if "_apply" in request.POST:
|
|
1618
|
+
for obj in selected_objects:
|
|
1619
|
+
obj.name = obj.new_name
|
|
1620
|
+
obj.save()
|
|
1621
|
+
|
|
1622
|
+
# Enforce constrained permissions
|
|
1623
|
+
if self.get_queryset().filter(pk__in=renamed_pks).count() != len(selected_objects):
|
|
1624
|
+
raise ObjectDoesNotExist
|
|
1625
|
+
|
|
1626
|
+
messages.success(
|
|
1627
|
+
request,
|
|
1628
|
+
f"Renamed {len(selected_objects)} {self.queryset.model._meta.verbose_name_plural}",
|
|
1629
|
+
)
|
|
1630
|
+
return redirect(self.get_return_url(request))
|
|
1631
|
+
|
|
1632
|
+
except ObjectDoesNotExist:
|
|
1633
|
+
msg = "Object update failed due to object-level permissions violation"
|
|
1634
|
+
form.add_error(None, msg)
|
|
1635
|
+
|
|
1636
|
+
else:
|
|
1637
|
+
form = _Form(initial={"pk": query_pks})
|
|
1638
|
+
|
|
1639
|
+
return Response(
|
|
1640
|
+
{
|
|
1641
|
+
"template": "generic/object_bulk_rename.html",
|
|
1642
|
+
"form": form,
|
|
1643
|
+
"obj_type_plural": self.queryset.model._meta.verbose_name_plural,
|
|
1644
|
+
"selected_objects": selected_objects,
|
|
1645
|
+
"return_url": self.get_return_url(request),
|
|
1646
|
+
"parent_name": self.get_selected_objects_parents_name(selected_objects),
|
|
1647
|
+
}
|
|
1648
|
+
)
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
class ModuleBayTemplateUIViewSet(
|
|
1652
|
+
ModuleBayCommonViewSetMixin,
|
|
1653
|
+
ObjectEditViewMixin,
|
|
1654
|
+
ObjectDestroyViewMixin,
|
|
1655
|
+
ObjectBulkDestroyViewMixin,
|
|
1656
|
+
ObjectBulkUpdateViewMixin,
|
|
1657
|
+
):
|
|
1658
|
+
queryset = ModuleBayTemplate.objects.all()
|
|
1659
|
+
filterset_class = filters.ModuleBayTemplateFilterSet
|
|
1660
|
+
bulk_update_form_class = forms.ModuleBayTemplateBulkEditForm
|
|
1661
|
+
create_form_class = forms.ModuleBayTemplateCreateForm
|
|
1662
|
+
form_class = forms.ModuleBayTemplateForm
|
|
1663
|
+
model_form_class = forms.ModuleBayTemplateForm
|
|
1664
|
+
serializer_class = serializers.ModuleBayTemplateSerializer
|
|
1665
|
+
table_class = tables.ModuleBayTemplateTable
|
|
1666
|
+
create_template_name = "dcim/device_component_add.html"
|
|
1667
|
+
|
|
1668
|
+
def get_selected_objects_parents_name(self, selected_objects):
|
|
1669
|
+
selected_object = selected_objects.first()
|
|
1670
|
+
if selected_object:
|
|
1671
|
+
parent = selected_object.device_type or selected_object.module_type
|
|
1672
|
+
return parent.display
|
|
1673
|
+
return ""
|
|
1674
|
+
|
|
1675
|
+
@action(detail=False, methods=["GET", "POST"], url_path="rename", url_name="bulk_rename")
|
|
1676
|
+
def bulk_rename(self, request, *args, **kwargs):
|
|
1677
|
+
return self._bulk_rename(request, *args, **kwargs)
|
|
1678
|
+
|
|
1679
|
+
|
|
1205
1680
|
#
|
|
1206
1681
|
# Platforms
|
|
1207
1682
|
#
|
|
1208
1683
|
|
|
1209
1684
|
|
|
1210
1685
|
class PlatformListView(generic.ObjectListView):
|
|
1211
|
-
queryset = Platform.objects.
|
|
1212
|
-
device_count=count_related(Device, "platform"),
|
|
1213
|
-
virtual_machine_count=count_related(VirtualMachine, "platform"),
|
|
1214
|
-
)
|
|
1686
|
+
queryset = Platform.objects.all()
|
|
1215
1687
|
filterset = filters.PlatformFilterSet
|
|
1216
1688
|
filterset_form = forms.PlatformFilterForm
|
|
1217
1689
|
table = tables.PlatformTable
|
|
@@ -1330,48 +1802,65 @@ class DeviceView(generic.ObjectView):
|
|
|
1330
1802
|
else:
|
|
1331
1803
|
software_version_images = []
|
|
1332
1804
|
|
|
1805
|
+
modulebay_count = instance.module_bays.count()
|
|
1806
|
+
module_count = instance.module_bays.filter(installed_module__isnull=False).count()
|
|
1807
|
+
|
|
1333
1808
|
return {
|
|
1334
1809
|
"services": services,
|
|
1335
1810
|
"software_version_images": software_version_images,
|
|
1336
1811
|
"vc_members": vc_members,
|
|
1337
1812
|
"vrf_table": vrf_table,
|
|
1338
1813
|
"active_tab": "device",
|
|
1814
|
+
"modulebay_count": modulebay_count,
|
|
1815
|
+
"module_count": f"{module_count}/{modulebay_count}",
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
|
|
1819
|
+
class DeviceComponentTabView(generic.ObjectView):
|
|
1820
|
+
queryset = Device.objects.all()
|
|
1821
|
+
|
|
1822
|
+
def get_extra_context(self, request, instance):
|
|
1823
|
+
modulebay_count = instance.module_bays.count()
|
|
1824
|
+
module_count = instance.module_bays.filter(installed_module__isnull=False).count()
|
|
1825
|
+
|
|
1826
|
+
return {
|
|
1827
|
+
"modulebay_count": modulebay_count,
|
|
1828
|
+
"module_count": f"{module_count}/{modulebay_count}",
|
|
1339
1829
|
}
|
|
1340
1830
|
|
|
1341
1831
|
|
|
1342
|
-
class DeviceConsolePortsView(
|
|
1832
|
+
class DeviceConsolePortsView(DeviceComponentTabView):
|
|
1343
1833
|
queryset = Device.objects.all()
|
|
1344
1834
|
template_name = "dcim/device/consoleports.html"
|
|
1345
1835
|
|
|
1346
1836
|
def get_extra_context(self, request, instance):
|
|
1347
1837
|
consoleports = (
|
|
1348
|
-
|
|
1349
|
-
.filter(device=instance)
|
|
1838
|
+
instance.all_console_ports.restrict(request.user, "view")
|
|
1350
1839
|
.select_related("cable")
|
|
1351
1840
|
.prefetch_related("_path__destination")
|
|
1352
1841
|
)
|
|
1353
|
-
consoleport_table = tables.
|
|
1842
|
+
consoleport_table = tables.DeviceModuleConsolePortTable(data=consoleports, user=request.user, orderable=False)
|
|
1354
1843
|
if request.user.has_perm("dcim.change_consoleport") or request.user.has_perm("dcim.delete_consoleport"):
|
|
1355
1844
|
consoleport_table.columns.show("pk")
|
|
1356
1845
|
|
|
1357
1846
|
return {
|
|
1847
|
+
**super().get_extra_context(request, instance),
|
|
1358
1848
|
"consoleport_table": consoleport_table,
|
|
1359
1849
|
"active_tab": "console-ports",
|
|
1360
1850
|
}
|
|
1361
1851
|
|
|
1362
1852
|
|
|
1363
|
-
class DeviceConsoleServerPortsView(
|
|
1853
|
+
class DeviceConsoleServerPortsView(DeviceComponentTabView):
|
|
1364
1854
|
queryset = Device.objects.all()
|
|
1365
1855
|
template_name = "dcim/device/consoleserverports.html"
|
|
1366
1856
|
|
|
1367
1857
|
def get_extra_context(self, request, instance):
|
|
1368
1858
|
consoleserverports = (
|
|
1369
|
-
|
|
1370
|
-
.filter(device=instance)
|
|
1859
|
+
instance.all_console_server_ports.restrict(request.user, "view")
|
|
1371
1860
|
.select_related("cable")
|
|
1372
1861
|
.prefetch_related("_path__destination")
|
|
1373
1862
|
)
|
|
1374
|
-
consoleserverport_table = tables.
|
|
1863
|
+
consoleserverport_table = tables.DeviceModuleConsoleServerPortTable(
|
|
1375
1864
|
data=consoleserverports, user=request.user, orderable=False
|
|
1376
1865
|
)
|
|
1377
1866
|
if request.user.has_perm("dcim.change_consoleserverport") or request.user.has_perm(
|
|
@@ -1380,60 +1869,61 @@ class DeviceConsoleServerPortsView(generic.ObjectView):
|
|
|
1380
1869
|
consoleserverport_table.columns.show("pk")
|
|
1381
1870
|
|
|
1382
1871
|
return {
|
|
1872
|
+
**super().get_extra_context(request, instance),
|
|
1383
1873
|
"consoleserverport_table": consoleserverport_table,
|
|
1384
1874
|
"active_tab": "console-server-ports",
|
|
1385
1875
|
}
|
|
1386
1876
|
|
|
1387
1877
|
|
|
1388
|
-
class DevicePowerPortsView(
|
|
1878
|
+
class DevicePowerPortsView(DeviceComponentTabView):
|
|
1389
1879
|
queryset = Device.objects.all()
|
|
1390
1880
|
template_name = "dcim/device/powerports.html"
|
|
1391
1881
|
|
|
1392
1882
|
def get_extra_context(self, request, instance):
|
|
1393
1883
|
powerports = (
|
|
1394
|
-
|
|
1395
|
-
.filter(device=instance)
|
|
1884
|
+
instance.all_power_ports.restrict(request.user, "view")
|
|
1396
1885
|
.select_related("cable")
|
|
1397
1886
|
.prefetch_related("_path__destination")
|
|
1398
1887
|
)
|
|
1399
|
-
powerport_table = tables.
|
|
1888
|
+
powerport_table = tables.DeviceModulePowerPortTable(data=powerports, user=request.user, orderable=False)
|
|
1400
1889
|
if request.user.has_perm("dcim.change_powerport") or request.user.has_perm("dcim.delete_powerport"):
|
|
1401
1890
|
powerport_table.columns.show("pk")
|
|
1402
1891
|
|
|
1403
1892
|
return {
|
|
1893
|
+
**super().get_extra_context(request, instance),
|
|
1404
1894
|
"powerport_table": powerport_table,
|
|
1405
1895
|
"active_tab": "power-ports",
|
|
1406
1896
|
}
|
|
1407
1897
|
|
|
1408
1898
|
|
|
1409
|
-
class DevicePowerOutletsView(
|
|
1899
|
+
class DevicePowerOutletsView(DeviceComponentTabView):
|
|
1410
1900
|
queryset = Device.objects.all()
|
|
1411
1901
|
template_name = "dcim/device/poweroutlets.html"
|
|
1412
1902
|
|
|
1413
1903
|
def get_extra_context(self, request, instance):
|
|
1414
1904
|
poweroutlets = (
|
|
1415
|
-
|
|
1416
|
-
.filter(device=instance)
|
|
1905
|
+
instance.all_power_outlets.restrict(request.user, "view")
|
|
1417
1906
|
.select_related("cable", "power_port")
|
|
1418
1907
|
.prefetch_related("_path__destination")
|
|
1419
1908
|
)
|
|
1420
|
-
poweroutlet_table = tables.
|
|
1909
|
+
poweroutlet_table = tables.DeviceModulePowerOutletTable(data=poweroutlets, user=request.user, orderable=False)
|
|
1421
1910
|
if request.user.has_perm("dcim.change_poweroutlet") or request.user.has_perm("dcim.delete_poweroutlet"):
|
|
1422
1911
|
poweroutlet_table.columns.show("pk")
|
|
1423
1912
|
|
|
1424
1913
|
return {
|
|
1914
|
+
**super().get_extra_context(request, instance),
|
|
1425
1915
|
"poweroutlet_table": poweroutlet_table,
|
|
1426
1916
|
"active_tab": "power-outlets",
|
|
1427
1917
|
}
|
|
1428
1918
|
|
|
1429
1919
|
|
|
1430
|
-
class DeviceInterfacesView(
|
|
1920
|
+
class DeviceInterfacesView(DeviceComponentTabView):
|
|
1431
1921
|
queryset = Device.objects.all()
|
|
1432
1922
|
template_name = "dcim/device/interfaces.html"
|
|
1433
1923
|
|
|
1434
1924
|
def get_extra_context(self, request, instance):
|
|
1435
1925
|
interfaces = (
|
|
1436
|
-
instance.
|
|
1926
|
+
instance.all_interfaces.restrict(request.user, "view")
|
|
1437
1927
|
.prefetch_related(
|
|
1438
1928
|
Prefetch("ip_addresses", queryset=IPAddress.objects.restrict(request.user)),
|
|
1439
1929
|
Prefetch("member_interfaces", queryset=Interface.objects.restrict(request.user)),
|
|
@@ -1441,56 +1931,56 @@ class DeviceInterfacesView(generic.ObjectView):
|
|
|
1441
1931
|
"tags",
|
|
1442
1932
|
)
|
|
1443
1933
|
.select_related("lag", "cable")
|
|
1934
|
+
.order_by("_name")
|
|
1444
1935
|
)
|
|
1445
|
-
interface_table = tables.
|
|
1936
|
+
interface_table = tables.DeviceModuleInterfaceTable(data=interfaces, user=request.user, orderable=False)
|
|
1446
1937
|
if VirtualChassis.objects.filter(master=instance).exists():
|
|
1447
1938
|
interface_table.columns.show("device")
|
|
1448
1939
|
if request.user.has_perm("dcim.change_interface") or request.user.has_perm("dcim.delete_interface"):
|
|
1449
1940
|
interface_table.columns.show("pk")
|
|
1450
1941
|
|
|
1451
1942
|
return {
|
|
1943
|
+
**super().get_extra_context(request, instance),
|
|
1452
1944
|
"interface_table": interface_table,
|
|
1453
1945
|
"active_tab": "interfaces",
|
|
1454
1946
|
}
|
|
1455
1947
|
|
|
1456
1948
|
|
|
1457
|
-
class DeviceFrontPortsView(
|
|
1949
|
+
class DeviceFrontPortsView(DeviceComponentTabView):
|
|
1458
1950
|
queryset = Device.objects.all()
|
|
1459
1951
|
template_name = "dcim/device/frontports.html"
|
|
1460
1952
|
|
|
1461
1953
|
def get_extra_context(self, request, instance):
|
|
1462
|
-
frontports = (
|
|
1463
|
-
|
|
1464
|
-
.filter(device=instance)
|
|
1465
|
-
.select_related("cable", "rear_port")
|
|
1466
|
-
)
|
|
1467
|
-
frontport_table = tables.DeviceFrontPortTable(data=frontports, user=request.user, orderable=False)
|
|
1954
|
+
frontports = instance.all_front_ports.restrict(request.user, "view").select_related("cable", "rear_port")
|
|
1955
|
+
frontport_table = tables.DeviceModuleFrontPortTable(data=frontports, user=request.user, orderable=False)
|
|
1468
1956
|
if request.user.has_perm("dcim.change_frontport") or request.user.has_perm("dcim.delete_frontport"):
|
|
1469
1957
|
frontport_table.columns.show("pk")
|
|
1470
1958
|
|
|
1471
1959
|
return {
|
|
1960
|
+
**super().get_extra_context(request, instance),
|
|
1472
1961
|
"frontport_table": frontport_table,
|
|
1473
1962
|
"active_tab": "front-ports",
|
|
1474
1963
|
}
|
|
1475
1964
|
|
|
1476
1965
|
|
|
1477
|
-
class DeviceRearPortsView(
|
|
1966
|
+
class DeviceRearPortsView(DeviceComponentTabView):
|
|
1478
1967
|
queryset = Device.objects.all()
|
|
1479
1968
|
template_name = "dcim/device/rearports.html"
|
|
1480
1969
|
|
|
1481
1970
|
def get_extra_context(self, request, instance):
|
|
1482
|
-
rearports =
|
|
1483
|
-
rearport_table = tables.
|
|
1971
|
+
rearports = instance.all_rear_ports.restrict(request.user, "view").select_related("cable")
|
|
1972
|
+
rearport_table = tables.DeviceModuleRearPortTable(data=rearports, user=request.user, orderable=False)
|
|
1484
1973
|
if request.user.has_perm("dcim.change_rearport") or request.user.has_perm("dcim.delete_rearport"):
|
|
1485
1974
|
rearport_table.columns.show("pk")
|
|
1486
1975
|
|
|
1487
1976
|
return {
|
|
1977
|
+
**super().get_extra_context(request, instance),
|
|
1488
1978
|
"rearport_table": rearport_table,
|
|
1489
1979
|
"active_tab": "rear-ports",
|
|
1490
1980
|
}
|
|
1491
1981
|
|
|
1492
1982
|
|
|
1493
|
-
class DeviceDeviceBaysView(
|
|
1983
|
+
class DeviceDeviceBaysView(DeviceComponentTabView):
|
|
1494
1984
|
queryset = Device.objects.all()
|
|
1495
1985
|
template_name = "dcim/device/devicebays.html"
|
|
1496
1986
|
|
|
@@ -1507,11 +1997,34 @@ class DeviceDeviceBaysView(generic.ObjectView):
|
|
|
1507
1997
|
devicebay_table.columns.show("pk")
|
|
1508
1998
|
|
|
1509
1999
|
return {
|
|
2000
|
+
**super().get_extra_context(request, instance),
|
|
1510
2001
|
"devicebay_table": devicebay_table,
|
|
1511
2002
|
"active_tab": "device-bays",
|
|
1512
2003
|
}
|
|
1513
2004
|
|
|
1514
2005
|
|
|
2006
|
+
class DeviceModuleBaysView(DeviceComponentTabView):
|
|
2007
|
+
queryset = Device.objects.all()
|
|
2008
|
+
template_name = "dcim/device/modulebays.html"
|
|
2009
|
+
|
|
2010
|
+
def get_extra_context(self, request, instance):
|
|
2011
|
+
# note: Device modules tab shouldn't show descendant modules until a proper tree view is implemented
|
|
2012
|
+
modulebays = (
|
|
2013
|
+
ModuleBay.objects.restrict(request.user, "view")
|
|
2014
|
+
.filter(parent_device=instance)
|
|
2015
|
+
.prefetch_related("installed_module__status", "installed_module")
|
|
2016
|
+
)
|
|
2017
|
+
modulebay_table = tables.DeviceModuleBayTable(data=modulebays, user=request.user, orderable=False)
|
|
2018
|
+
if request.user.has_perm("dcim.change_modulebay") or request.user.has_perm("dcim.delete_modulebay"):
|
|
2019
|
+
modulebay_table.columns.show("pk")
|
|
2020
|
+
|
|
2021
|
+
return {
|
|
2022
|
+
**super().get_extra_context(request, instance),
|
|
2023
|
+
"modulebay_table": modulebay_table,
|
|
2024
|
+
"active_tab": "module-bays",
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
|
|
1515
2028
|
class DeviceInventoryView(generic.ObjectView):
|
|
1516
2029
|
queryset = Device.objects.all()
|
|
1517
2030
|
template_name = "dcim/device/inventory.html"
|
|
@@ -1548,7 +2061,7 @@ class DeviceLLDPNeighborsView(generic.ObjectView):
|
|
|
1548
2061
|
|
|
1549
2062
|
def get_extra_context(self, request, instance):
|
|
1550
2063
|
interfaces = (
|
|
1551
|
-
instance.
|
|
2064
|
+
instance.all_interfaces.restrict(request.user, "view")
|
|
1552
2065
|
.prefetch_related("_path__destination")
|
|
1553
2066
|
.exclude(type__in=NONCONNECTABLE_IFACE_TYPES)
|
|
1554
2067
|
)
|
|
@@ -1585,7 +2098,7 @@ class DeviceChangeLogView(ObjectChangeLogView):
|
|
|
1585
2098
|
base_template = "dcim/device/base.html"
|
|
1586
2099
|
|
|
1587
2100
|
|
|
1588
|
-
class DeviceDynamicGroupsView(ObjectDynamicGroupsView):
|
|
2101
|
+
class DeviceDynamicGroupsView(ObjectDynamicGroupsView): # 3.0 TODO: remove, deprecated in 2.3
|
|
1589
2102
|
base_template = "dcim/device/base.html"
|
|
1590
2103
|
|
|
1591
2104
|
|
|
@@ -1626,6 +2139,432 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
|
|
|
1626
2139
|
table = tables.DeviceTable
|
|
1627
2140
|
|
|
1628
2141
|
|
|
2142
|
+
#
|
|
2143
|
+
# Modules
|
|
2144
|
+
#
|
|
2145
|
+
|
|
2146
|
+
|
|
2147
|
+
class BulkComponentCreateUIViewSetMixin:
|
|
2148
|
+
def _bulk_component_create(self, request, component_queryset, bulk_component_form, parent_field=None):
|
|
2149
|
+
parent_model_name = self.queryset.model._meta.verbose_name_plural
|
|
2150
|
+
if parent_field is None:
|
|
2151
|
+
parent_field = self.queryset.model._meta.model_name
|
|
2152
|
+
model_name = component_queryset.model._meta.verbose_name_plural
|
|
2153
|
+
model = component_queryset.model
|
|
2154
|
+
component_create_form = get_form_for_model(model)
|
|
2155
|
+
|
|
2156
|
+
# Are we editing *all* objects in the queryset or just a selected subset?
|
|
2157
|
+
if request.POST.get("_all") and self.filterset is not None:
|
|
2158
|
+
pk_list = [obj.pk for obj in self.filterset(request.GET, self.get_queryset().only("pk")).qs]
|
|
2159
|
+
else:
|
|
2160
|
+
pk_list = request.POST.getlist("pk")
|
|
2161
|
+
|
|
2162
|
+
selected_objects = self.get_queryset().filter(pk__in=pk_list)
|
|
2163
|
+
if not selected_objects:
|
|
2164
|
+
messages.warning(
|
|
2165
|
+
request,
|
|
2166
|
+
f"No {parent_model_name} were selected.",
|
|
2167
|
+
)
|
|
2168
|
+
return redirect(self.get_return_url(request))
|
|
2169
|
+
table = self.table_class(selected_objects)
|
|
2170
|
+
|
|
2171
|
+
if "_create" in request.POST:
|
|
2172
|
+
form = bulk_component_form(model, request.POST)
|
|
2173
|
+
|
|
2174
|
+
if form.is_valid():
|
|
2175
|
+
new_components = []
|
|
2176
|
+
data = deepcopy(form.cleaned_data)
|
|
2177
|
+
|
|
2178
|
+
try:
|
|
2179
|
+
with transaction.atomic():
|
|
2180
|
+
for obj in data["pk"]:
|
|
2181
|
+
names = data["name_pattern"]
|
|
2182
|
+
labels = data["label_pattern"] if "label_pattern" in data else None
|
|
2183
|
+
for i, name in enumerate(names):
|
|
2184
|
+
label = labels[i] if labels else None
|
|
2185
|
+
|
|
2186
|
+
component_data = {
|
|
2187
|
+
parent_field: obj.pk,
|
|
2188
|
+
"name": name,
|
|
2189
|
+
"label": label,
|
|
2190
|
+
}
|
|
2191
|
+
component_data.update(data)
|
|
2192
|
+
component_form = component_create_form(component_data)
|
|
2193
|
+
if component_form.is_valid():
|
|
2194
|
+
instance = component_form.save()
|
|
2195
|
+
new_components.append(instance)
|
|
2196
|
+
else:
|
|
2197
|
+
for (
|
|
2198
|
+
field,
|
|
2199
|
+
errors,
|
|
2200
|
+
) in component_form.errors.as_data().items():
|
|
2201
|
+
for e in errors:
|
|
2202
|
+
err_str = ", ".join(e)
|
|
2203
|
+
form.add_error(
|
|
2204
|
+
field,
|
|
2205
|
+
f"{obj} {name}: {err_str}",
|
|
2206
|
+
)
|
|
2207
|
+
|
|
2208
|
+
# Enforce object-level permissions
|
|
2209
|
+
if component_queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(
|
|
2210
|
+
new_components
|
|
2211
|
+
):
|
|
2212
|
+
raise ObjectDoesNotExist
|
|
2213
|
+
|
|
2214
|
+
except IntegrityError:
|
|
2215
|
+
pass
|
|
2216
|
+
|
|
2217
|
+
except ObjectDoesNotExist:
|
|
2218
|
+
msg = "Component creation failed due to object-level permissions violation"
|
|
2219
|
+
form.add_error(None, msg)
|
|
2220
|
+
|
|
2221
|
+
if not form.errors:
|
|
2222
|
+
msg = f"Added {len(new_components)} {model_name} to {len(form.cleaned_data['pk'])} {parent_model_name}."
|
|
2223
|
+
messages.success(request, msg)
|
|
2224
|
+
|
|
2225
|
+
return redirect(self.get_return_url(request))
|
|
2226
|
+
|
|
2227
|
+
else:
|
|
2228
|
+
form = bulk_component_form(model, initial={"pk": pk_list})
|
|
2229
|
+
|
|
2230
|
+
return Response(
|
|
2231
|
+
{
|
|
2232
|
+
"template": "generic/object_bulk_add_component.html",
|
|
2233
|
+
"form": form,
|
|
2234
|
+
"parent_model_name": parent_model_name,
|
|
2235
|
+
"model_name": model_name,
|
|
2236
|
+
"table": table,
|
|
2237
|
+
"return_url": self.get_return_url(request),
|
|
2238
|
+
},
|
|
2239
|
+
)
|
|
2240
|
+
|
|
2241
|
+
|
|
2242
|
+
class ModuleUIViewSet(BulkComponentCreateUIViewSetMixin, NautobotUIViewSet):
|
|
2243
|
+
queryset = Module.objects.all()
|
|
2244
|
+
filterset_class = filters.ModuleFilterSet
|
|
2245
|
+
filterset_form_class = forms.ModuleFilterForm
|
|
2246
|
+
form_class = forms.ModuleForm
|
|
2247
|
+
bulk_update_form_class = forms.ModuleBulkEditForm
|
|
2248
|
+
serializer_class = serializers.ModuleSerializer
|
|
2249
|
+
table_class = tables.ModuleTable
|
|
2250
|
+
component_model = None
|
|
2251
|
+
|
|
2252
|
+
def get_action(self):
|
|
2253
|
+
if self.component_model:
|
|
2254
|
+
method = self.request.method.lower()
|
|
2255
|
+
if method == "get":
|
|
2256
|
+
return "view"
|
|
2257
|
+
else:
|
|
2258
|
+
return "change"
|
|
2259
|
+
|
|
2260
|
+
return super().get_action()
|
|
2261
|
+
|
|
2262
|
+
def get_required_permission(self):
|
|
2263
|
+
# TODO: standardize a pattern for permissions enforcement on custom actions
|
|
2264
|
+
if self.component_model:
|
|
2265
|
+
model = self.component_model
|
|
2266
|
+
method = self.request.method.lower()
|
|
2267
|
+
if method == "get":
|
|
2268
|
+
component_action = "view"
|
|
2269
|
+
permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.view_module"]
|
|
2270
|
+
elif self.action.startswith("bulk_add"):
|
|
2271
|
+
component_action = "add"
|
|
2272
|
+
permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.change_module"]
|
|
2273
|
+
else:
|
|
2274
|
+
component_action = "change"
|
|
2275
|
+
permissions = [*self.get_permissions_for_model(model, [component_action]), "dcim.change_module"]
|
|
2276
|
+
|
|
2277
|
+
return permissions
|
|
2278
|
+
|
|
2279
|
+
return super().get_required_permission()
|
|
2280
|
+
|
|
2281
|
+
def get_extra_context(self, request, instance):
|
|
2282
|
+
context = super().get_extra_context(request, instance)
|
|
2283
|
+
if instance:
|
|
2284
|
+
context["modulebay_count"] = instance.module_bays.count()
|
|
2285
|
+
populated_module_count = instance.module_bays.filter(installed_module__isnull=False).count()
|
|
2286
|
+
context["module_count"] = f"{populated_module_count}/{context['modulebay_count']}"
|
|
2287
|
+
if self.action in ["create", "update"]:
|
|
2288
|
+
context["active_parent_tab"] = self._get_edit_view_active_parent_tab(request)
|
|
2289
|
+
return context
|
|
2290
|
+
|
|
2291
|
+
def _get_edit_view_active_parent_tab(self, request):
|
|
2292
|
+
active_parent_tab = "device"
|
|
2293
|
+
form_class = self.get_form_class()
|
|
2294
|
+
form = form_class(
|
|
2295
|
+
data=request.POST,
|
|
2296
|
+
files=request.FILES,
|
|
2297
|
+
initial=normalize_querydict(request.GET, form_class=form_class),
|
|
2298
|
+
instance=self.get_object(),
|
|
2299
|
+
)
|
|
2300
|
+
if form["parent_module_bay_module"].initial:
|
|
2301
|
+
active_parent_tab = "module"
|
|
2302
|
+
elif form["location"].initial:
|
|
2303
|
+
active_parent_tab = "location"
|
|
2304
|
+
|
|
2305
|
+
return active_parent_tab
|
|
2306
|
+
|
|
2307
|
+
@action(detail=True, url_path="console-ports", component_model=ConsolePort)
|
|
2308
|
+
def consoleports(self, request, *args, **kwargs):
|
|
2309
|
+
instance = self.get_object()
|
|
2310
|
+
consoleports = (
|
|
2311
|
+
instance.console_ports.restrict(request.user, "view")
|
|
2312
|
+
.select_related("cable")
|
|
2313
|
+
.prefetch_related("_path__destination")
|
|
2314
|
+
)
|
|
2315
|
+
consoleport_table = tables.DeviceModuleConsolePortTable(data=consoleports, user=request.user, orderable=False)
|
|
2316
|
+
if request.user.has_perm("dcim.change_consoleport") or request.user.has_perm("dcim.delete_consoleport"):
|
|
2317
|
+
consoleport_table.columns.show("pk")
|
|
2318
|
+
|
|
2319
|
+
return Response(
|
|
2320
|
+
{
|
|
2321
|
+
"consoleport_table": consoleport_table,
|
|
2322
|
+
"active_tab": "console-ports",
|
|
2323
|
+
}
|
|
2324
|
+
)
|
|
2325
|
+
|
|
2326
|
+
@action(detail=True, url_path="console-server-ports", component_model=ConsoleServerPort)
|
|
2327
|
+
def consoleserverports(self, request, *args, **kwargs):
|
|
2328
|
+
instance = self.get_object()
|
|
2329
|
+
consoleserverports = (
|
|
2330
|
+
instance.console_server_ports.restrict(request.user, "view")
|
|
2331
|
+
.select_related("cable")
|
|
2332
|
+
.prefetch_related("_path__destination")
|
|
2333
|
+
)
|
|
2334
|
+
consoleserverport_table = tables.DeviceModuleConsoleServerPortTable(
|
|
2335
|
+
data=consoleserverports, user=request.user, orderable=False, parent_module=instance
|
|
2336
|
+
)
|
|
2337
|
+
if request.user.has_perm("dcim.change_consoleserverport") or request.user.has_perm(
|
|
2338
|
+
"dcim.delete_consoleserverport"
|
|
2339
|
+
):
|
|
2340
|
+
consoleserverport_table.columns.show("pk")
|
|
2341
|
+
|
|
2342
|
+
return Response(
|
|
2343
|
+
{
|
|
2344
|
+
"consoleserverport_table": consoleserverport_table,
|
|
2345
|
+
"active_tab": "console-server-ports",
|
|
2346
|
+
}
|
|
2347
|
+
)
|
|
2348
|
+
|
|
2349
|
+
@action(detail=True, url_path="power-ports", component_model=PowerPort)
|
|
2350
|
+
def powerports(self, request, *args, **kwargs):
|
|
2351
|
+
instance = self.get_object()
|
|
2352
|
+
powerports = (
|
|
2353
|
+
instance.power_ports.restrict(request.user, "view")
|
|
2354
|
+
.select_related("cable")
|
|
2355
|
+
.prefetch_related("_path__destination")
|
|
2356
|
+
)
|
|
2357
|
+
powerport_table = tables.DeviceModulePowerPortTable(
|
|
2358
|
+
data=powerports, user=request.user, orderable=False, parent_module=instance
|
|
2359
|
+
)
|
|
2360
|
+
if request.user.has_perm("dcim.change_powerport") or request.user.has_perm("dcim.delete_powerport"):
|
|
2361
|
+
powerport_table.columns.show("pk")
|
|
2362
|
+
|
|
2363
|
+
return Response(
|
|
2364
|
+
{
|
|
2365
|
+
"powerport_table": powerport_table,
|
|
2366
|
+
"active_tab": "power-ports",
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
|
|
2370
|
+
@action(detail=True, url_path="power-outlets", component_model=PowerOutlet)
|
|
2371
|
+
def poweroutlets(self, request, *args, **kwargs):
|
|
2372
|
+
instance = self.get_object()
|
|
2373
|
+
poweroutlets = (
|
|
2374
|
+
instance.power_outlets.restrict(request.user, "view")
|
|
2375
|
+
.select_related("cable", "power_port")
|
|
2376
|
+
.prefetch_related("_path__destination")
|
|
2377
|
+
)
|
|
2378
|
+
poweroutlet_table = tables.DeviceModulePowerOutletTable(
|
|
2379
|
+
data=poweroutlets, user=request.user, orderable=False, parent_module=instance
|
|
2380
|
+
)
|
|
2381
|
+
if request.user.has_perm("dcim.change_poweroutlet") or request.user.has_perm("dcim.delete_poweroutlet"):
|
|
2382
|
+
poweroutlet_table.columns.show("pk")
|
|
2383
|
+
|
|
2384
|
+
return Response(
|
|
2385
|
+
{
|
|
2386
|
+
"poweroutlet_table": poweroutlet_table,
|
|
2387
|
+
"active_tab": "power-outlets",
|
|
2388
|
+
}
|
|
2389
|
+
)
|
|
2390
|
+
|
|
2391
|
+
@action(detail=True, component_model=Interface)
|
|
2392
|
+
def interfaces(self, request, *args, **kwargs):
|
|
2393
|
+
instance = self.get_object()
|
|
2394
|
+
interfaces = (
|
|
2395
|
+
instance.interfaces.restrict(request.user, "view")
|
|
2396
|
+
.prefetch_related(
|
|
2397
|
+
Prefetch("ip_addresses", queryset=IPAddress.objects.restrict(request.user)),
|
|
2398
|
+
Prefetch("member_interfaces", queryset=Interface.objects.restrict(request.user)),
|
|
2399
|
+
"_path__destination",
|
|
2400
|
+
"tags",
|
|
2401
|
+
)
|
|
2402
|
+
.select_related("lag", "cable")
|
|
2403
|
+
)
|
|
2404
|
+
interface_table = tables.DeviceModuleInterfaceTable(
|
|
2405
|
+
data=interfaces, user=request.user, orderable=False, parent_module=instance
|
|
2406
|
+
)
|
|
2407
|
+
if request.user.has_perm("dcim.change_interface") or request.user.has_perm("dcim.delete_interface"):
|
|
2408
|
+
interface_table.columns.show("pk")
|
|
2409
|
+
|
|
2410
|
+
return Response(
|
|
2411
|
+
{
|
|
2412
|
+
"interface_table": interface_table,
|
|
2413
|
+
"active_tab": "interfaces",
|
|
2414
|
+
}
|
|
2415
|
+
)
|
|
2416
|
+
|
|
2417
|
+
@action(detail=True, url_path="front-ports", component_model=FrontPort)
|
|
2418
|
+
def frontports(self, request, *args, **kwargs):
|
|
2419
|
+
instance = self.get_object()
|
|
2420
|
+
frontports = instance.front_ports.restrict(request.user, "view").select_related("cable", "rear_port")
|
|
2421
|
+
frontport_table = tables.DeviceModuleFrontPortTable(
|
|
2422
|
+
data=frontports, user=request.user, orderable=False, parent_module=instance
|
|
2423
|
+
)
|
|
2424
|
+
if request.user.has_perm("dcim.change_frontport") or request.user.has_perm("dcim.delete_frontport"):
|
|
2425
|
+
frontport_table.columns.show("pk")
|
|
2426
|
+
|
|
2427
|
+
return Response(
|
|
2428
|
+
{
|
|
2429
|
+
"frontport_table": frontport_table,
|
|
2430
|
+
"active_tab": "front-ports",
|
|
2431
|
+
},
|
|
2432
|
+
)
|
|
2433
|
+
|
|
2434
|
+
@action(detail=True, url_path="rear-ports", component_model=RearPort)
|
|
2435
|
+
def rearports(self, request, *args, **kwargs):
|
|
2436
|
+
instance = self.get_object()
|
|
2437
|
+
rearports = instance.rear_ports.restrict(request.user, "view").select_related("cable")
|
|
2438
|
+
rearport_table = tables.DeviceModuleRearPortTable(
|
|
2439
|
+
data=rearports, user=request.user, orderable=False, parent_module=instance
|
|
2440
|
+
)
|
|
2441
|
+
if request.user.has_perm("dcim.change_rearport") or request.user.has_perm("dcim.delete_rearport"):
|
|
2442
|
+
rearport_table.columns.show("pk")
|
|
2443
|
+
|
|
2444
|
+
return Response(
|
|
2445
|
+
{
|
|
2446
|
+
"rearport_table": rearport_table,
|
|
2447
|
+
"active_tab": "rear-ports",
|
|
2448
|
+
}
|
|
2449
|
+
)
|
|
2450
|
+
|
|
2451
|
+
@action(detail=True, url_path="module-bays", component_model=ModuleBay)
|
|
2452
|
+
def modulebays(self, request, *args, **kwargs):
|
|
2453
|
+
instance = self.get_object()
|
|
2454
|
+
modulebays = instance.module_bays.restrict(request.user, "view").prefetch_related(
|
|
2455
|
+
"installed_module__status", "installed_module"
|
|
2456
|
+
)
|
|
2457
|
+
modulebay_table = tables.ModuleModuleBayTable(data=modulebays, user=request.user, orderable=False)
|
|
2458
|
+
if request.user.has_perm("dcim.change_modulebay") or request.user.has_perm("dcim.delete_modulebay"):
|
|
2459
|
+
modulebay_table.columns.show("pk")
|
|
2460
|
+
|
|
2461
|
+
return Response(
|
|
2462
|
+
{
|
|
2463
|
+
"modulebay_table": modulebay_table,
|
|
2464
|
+
"active_tab": "module-bays",
|
|
2465
|
+
}
|
|
2466
|
+
)
|
|
2467
|
+
|
|
2468
|
+
@action(
|
|
2469
|
+
detail=False,
|
|
2470
|
+
methods=["POST"],
|
|
2471
|
+
url_path="console-ports/add",
|
|
2472
|
+
url_name="bulk_add_consoleport",
|
|
2473
|
+
component_model=ConsolePort,
|
|
2474
|
+
)
|
|
2475
|
+
def bulk_add_consoleport(self, request, *args, **kwargs):
|
|
2476
|
+
return self._bulk_component_create(
|
|
2477
|
+
request=request,
|
|
2478
|
+
component_queryset=ConsolePort.objects.all(),
|
|
2479
|
+
bulk_component_form=forms.ModuleConsolePortBulkCreateForm,
|
|
2480
|
+
)
|
|
2481
|
+
|
|
2482
|
+
@action(
|
|
2483
|
+
detail=False,
|
|
2484
|
+
methods=["POST"],
|
|
2485
|
+
url_path="console-server-ports/add",
|
|
2486
|
+
url_name="bulk_add_consoleserverport",
|
|
2487
|
+
component_model=ConsoleServerPort,
|
|
2488
|
+
)
|
|
2489
|
+
def bulk_add_consoleserverport(self, request, *args, **kwargs):
|
|
2490
|
+
return self._bulk_component_create(
|
|
2491
|
+
request=request,
|
|
2492
|
+
component_queryset=ConsoleServerPort.objects.all(),
|
|
2493
|
+
bulk_component_form=forms.ModuleConsoleServerPortBulkCreateForm,
|
|
2494
|
+
)
|
|
2495
|
+
|
|
2496
|
+
@action(
|
|
2497
|
+
detail=False,
|
|
2498
|
+
methods=["POST"],
|
|
2499
|
+
url_path="power-ports/add",
|
|
2500
|
+
url_name="bulk_add_powerport",
|
|
2501
|
+
component_model=PowerPort,
|
|
2502
|
+
)
|
|
2503
|
+
def bulk_add_powerport(self, request, *args, **kwargs):
|
|
2504
|
+
return self._bulk_component_create(
|
|
2505
|
+
request=request,
|
|
2506
|
+
component_queryset=PowerPort.objects.all(),
|
|
2507
|
+
bulk_component_form=forms.ModulePowerPortBulkCreateForm,
|
|
2508
|
+
)
|
|
2509
|
+
|
|
2510
|
+
@action(
|
|
2511
|
+
detail=False,
|
|
2512
|
+
methods=["POST"],
|
|
2513
|
+
url_path="power-outlets/add",
|
|
2514
|
+
url_name="bulk_add_poweroutlet",
|
|
2515
|
+
component_model=PowerOutlet,
|
|
2516
|
+
)
|
|
2517
|
+
def bulk_add_poweroutlet(self, request, *args, **kwargs):
|
|
2518
|
+
return self._bulk_component_create(
|
|
2519
|
+
request=request,
|
|
2520
|
+
component_queryset=PowerOutlet.objects.all(),
|
|
2521
|
+
bulk_component_form=forms.ModulePowerOutletBulkCreateForm,
|
|
2522
|
+
)
|
|
2523
|
+
|
|
2524
|
+
@action(
|
|
2525
|
+
detail=False,
|
|
2526
|
+
methods=["POST"],
|
|
2527
|
+
url_path="interfaces/add",
|
|
2528
|
+
url_name="bulk_add_interface",
|
|
2529
|
+
component_model=Interface,
|
|
2530
|
+
)
|
|
2531
|
+
def bulk_add_interface(self, request, *args, **kwargs):
|
|
2532
|
+
return self._bulk_component_create(
|
|
2533
|
+
request=request,
|
|
2534
|
+
component_queryset=Interface.objects.all(),
|
|
2535
|
+
bulk_component_form=forms.ModuleInterfaceBulkCreateForm,
|
|
2536
|
+
)
|
|
2537
|
+
|
|
2538
|
+
@action(
|
|
2539
|
+
detail=False,
|
|
2540
|
+
methods=["POST"],
|
|
2541
|
+
url_path="rear-ports/add",
|
|
2542
|
+
url_name="bulk_add_rearport",
|
|
2543
|
+
component_model=RearPort,
|
|
2544
|
+
)
|
|
2545
|
+
def bulk_add_rearport(self, request, *args, **kwargs):
|
|
2546
|
+
return self._bulk_component_create(
|
|
2547
|
+
request=request,
|
|
2548
|
+
component_queryset=RearPort.objects.all(),
|
|
2549
|
+
bulk_component_form=forms.ModuleRearPortBulkCreateForm,
|
|
2550
|
+
)
|
|
2551
|
+
|
|
2552
|
+
@action(
|
|
2553
|
+
detail=False,
|
|
2554
|
+
methods=["POST"],
|
|
2555
|
+
url_path="module-bays/add",
|
|
2556
|
+
url_name="bulk_add_modulebay",
|
|
2557
|
+
component_model=ModuleBay,
|
|
2558
|
+
)
|
|
2559
|
+
def bulk_add_modulebay(self, request, *args, **kwargs):
|
|
2560
|
+
return self._bulk_component_create(
|
|
2561
|
+
request=request,
|
|
2562
|
+
component_queryset=ModuleBay.objects.all(),
|
|
2563
|
+
bulk_component_form=forms.ModuleModuleBayBulkCreateForm,
|
|
2564
|
+
parent_field="parent_module",
|
|
2565
|
+
)
|
|
2566
|
+
|
|
2567
|
+
|
|
1629
2568
|
#
|
|
1630
2569
|
# Console ports
|
|
1631
2570
|
#
|
|
@@ -1643,7 +2582,11 @@ class ConsolePortView(generic.ObjectView):
|
|
|
1643
2582
|
queryset = ConsolePort.objects.all()
|
|
1644
2583
|
|
|
1645
2584
|
def get_extra_context(self, request, instance):
|
|
1646
|
-
return {
|
|
2585
|
+
return {
|
|
2586
|
+
"device_breadcrumb_url": "dcim:device_consoleports",
|
|
2587
|
+
"module_breadcrumb_url": "dcim:module_consoleports",
|
|
2588
|
+
**super().get_extra_context(request, instance),
|
|
2589
|
+
}
|
|
1647
2590
|
|
|
1648
2591
|
|
|
1649
2592
|
class ConsolePortCreateView(generic.ComponentCreateView):
|
|
@@ -1705,7 +2648,11 @@ class ConsoleServerPortView(generic.ObjectView):
|
|
|
1705
2648
|
queryset = ConsoleServerPort.objects.all()
|
|
1706
2649
|
|
|
1707
2650
|
def get_extra_context(self, request, instance):
|
|
1708
|
-
return {
|
|
2651
|
+
return {
|
|
2652
|
+
"device_breadcrumb_url": "dcim:device_consoleserverports",
|
|
2653
|
+
"module_breadcrumb_url": "dcim:module_consoleserverports",
|
|
2654
|
+
**super().get_extra_context(request, instance),
|
|
2655
|
+
}
|
|
1709
2656
|
|
|
1710
2657
|
|
|
1711
2658
|
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
|
@@ -1767,7 +2714,11 @@ class PowerPortView(generic.ObjectView):
|
|
|
1767
2714
|
queryset = PowerPort.objects.all()
|
|
1768
2715
|
|
|
1769
2716
|
def get_extra_context(self, request, instance):
|
|
1770
|
-
return {
|
|
2717
|
+
return {
|
|
2718
|
+
"device_breadcrumb_url": "dcim:device_powerports",
|
|
2719
|
+
"module_breadcrumb_url": "dcim:module_powerports",
|
|
2720
|
+
**super().get_extra_context(request, instance),
|
|
2721
|
+
}
|
|
1771
2722
|
|
|
1772
2723
|
|
|
1773
2724
|
class PowerPortCreateView(generic.ComponentCreateView):
|
|
@@ -1829,7 +2780,11 @@ class PowerOutletView(generic.ObjectView):
|
|
|
1829
2780
|
queryset = PowerOutlet.objects.all()
|
|
1830
2781
|
|
|
1831
2782
|
def get_extra_context(self, request, instance):
|
|
1832
|
-
return {
|
|
2783
|
+
return {
|
|
2784
|
+
"device_breadcrumb_url": "dcim:device_poweroutlets",
|
|
2785
|
+
"module_breadcrumb_url": "dcim:module_poweroutlets",
|
|
2786
|
+
**super().get_extra_context(request, instance),
|
|
2787
|
+
}
|
|
1833
2788
|
|
|
1834
2789
|
|
|
1835
2790
|
class PowerOutletCreateView(generic.ComponentCreateView):
|
|
@@ -1922,7 +2877,8 @@ class InterfaceView(generic.ObjectView):
|
|
|
1922
2877
|
return {
|
|
1923
2878
|
"ipaddress_table": ipaddress_table,
|
|
1924
2879
|
"vlan_table": vlan_table,
|
|
1925
|
-
"
|
|
2880
|
+
"device_breadcrumb_url": "dcim:device_interfaces",
|
|
2881
|
+
"module_breadcrumb_url": "dcim:module_interfaces",
|
|
1926
2882
|
"child_interfaces_table": child_interfaces_tables,
|
|
1927
2883
|
"redundancy_table": redundancy_table,
|
|
1928
2884
|
**super().get_extra_context(request, instance),
|
|
@@ -2012,7 +2968,11 @@ class FrontPortView(generic.ObjectView):
|
|
|
2012
2968
|
queryset = FrontPort.objects.all()
|
|
2013
2969
|
|
|
2014
2970
|
def get_extra_context(self, request, instance):
|
|
2015
|
-
return {
|
|
2971
|
+
return {
|
|
2972
|
+
"device_breadcrumb_url": "dcim:device_frontports",
|
|
2973
|
+
"module_breadcrumb_url": "dcim:module_frontports",
|
|
2974
|
+
**super().get_extra_context(request, instance),
|
|
2975
|
+
}
|
|
2016
2976
|
|
|
2017
2977
|
|
|
2018
2978
|
class FrontPortCreateView(generic.ComponentCreateView):
|
|
@@ -2074,7 +3034,11 @@ class RearPortView(generic.ObjectView):
|
|
|
2074
3034
|
queryset = RearPort.objects.all()
|
|
2075
3035
|
|
|
2076
3036
|
def get_extra_context(self, request, instance):
|
|
2077
|
-
return {
|
|
3037
|
+
return {
|
|
3038
|
+
"device_breadcrumb_url": "dcim:device_rearports",
|
|
3039
|
+
"module_breadcrumb_url": "dcim:module_rearports",
|
|
3040
|
+
**super().get_extra_context(request, instance),
|
|
3041
|
+
}
|
|
2078
3042
|
|
|
2079
3043
|
|
|
2080
3044
|
class RearPortCreateView(generic.ComponentCreateView):
|
|
@@ -2136,7 +3100,7 @@ class DeviceBayView(generic.ObjectView):
|
|
|
2136
3100
|
queryset = DeviceBay.objects.all()
|
|
2137
3101
|
|
|
2138
3102
|
def get_extra_context(self, request, instance):
|
|
2139
|
-
return {"
|
|
3103
|
+
return {"device_breadcrumb_url": "dcim:device_devicebays", **super().get_extra_context(request, instance)}
|
|
2140
3104
|
|
|
2141
3105
|
|
|
2142
3106
|
class DeviceBayCreateView(generic.ComponentCreateView):
|
|
@@ -2262,6 +3226,43 @@ class DeviceBayBulkDeleteView(generic.BulkDeleteView):
|
|
|
2262
3226
|
table = tables.DeviceBayTable
|
|
2263
3227
|
|
|
2264
3228
|
|
|
3229
|
+
#
|
|
3230
|
+
# Module bays
|
|
3231
|
+
#
|
|
3232
|
+
|
|
3233
|
+
|
|
3234
|
+
class ModuleBayUIViewSet(ModuleBayCommonViewSetMixin, NautobotUIViewSet):
|
|
3235
|
+
queryset = ModuleBay.objects.all()
|
|
3236
|
+
filterset_class = filters.ModuleBayFilterSet
|
|
3237
|
+
filterset_form_class = forms.ModuleBayFilterForm
|
|
3238
|
+
bulk_update_form_class = forms.ModuleBayBulkEditForm
|
|
3239
|
+
create_form_class = forms.ModuleBayCreateForm
|
|
3240
|
+
form_class = forms.ModuleBayForm
|
|
3241
|
+
model_form_class = forms.ModuleBayForm
|
|
3242
|
+
serializer_class = serializers.ModuleBaySerializer
|
|
3243
|
+
table_class = tables.ModuleBayTable
|
|
3244
|
+
create_template_name = "dcim/device_component_add.html"
|
|
3245
|
+
|
|
3246
|
+
def get_extra_context(self, request, instance):
|
|
3247
|
+
if instance:
|
|
3248
|
+
return {
|
|
3249
|
+
"device_breadcrumb_url": "dcim:device_modulebays",
|
|
3250
|
+
"module_breadcrumb_url": "dcim:module_modulebays",
|
|
3251
|
+
}
|
|
3252
|
+
return {}
|
|
3253
|
+
|
|
3254
|
+
def get_selected_objects_parents_name(self, selected_objects):
|
|
3255
|
+
selected_object = selected_objects.first()
|
|
3256
|
+
if selected_object:
|
|
3257
|
+
parent = selected_object.parent_device or selected_object.parent_module
|
|
3258
|
+
return parent.display
|
|
3259
|
+
return ""
|
|
3260
|
+
|
|
3261
|
+
@action(detail=False, methods=["GET", "POST"], url_path="rename", url_name="bulk_rename")
|
|
3262
|
+
def bulk_rename(self, request, *args, **kwargs):
|
|
3263
|
+
return self._bulk_rename(request, *args, **kwargs)
|
|
3264
|
+
|
|
3265
|
+
|
|
2265
3266
|
#
|
|
2266
3267
|
# Inventory items
|
|
2267
3268
|
#
|
|
@@ -2286,7 +3287,7 @@ class InventoryItemView(generic.ObjectView):
|
|
|
2286
3287
|
software_version_images = []
|
|
2287
3288
|
|
|
2288
3289
|
return {
|
|
2289
|
-
"
|
|
3290
|
+
"device_breadcrumb_url": "dcim:device_inventory",
|
|
2290
3291
|
"software_version_images": software_version_images,
|
|
2291
3292
|
**super().get_extra_context(request, instance),
|
|
2292
3293
|
}
|
|
@@ -2425,6 +3426,17 @@ class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
|
|
2425
3426
|
default_return_url = "dcim:device_list"
|
|
2426
3427
|
|
|
2427
3428
|
|
|
3429
|
+
class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
|
|
3430
|
+
parent_model = Device
|
|
3431
|
+
parent_field = "parent_device"
|
|
3432
|
+
form = forms.ModuleBayBulkCreateForm
|
|
3433
|
+
queryset = ModuleBay.objects.all()
|
|
3434
|
+
model_form = forms.ModuleBayForm
|
|
3435
|
+
filterset = filters.DeviceFilterSet
|
|
3436
|
+
table = tables.DeviceTable
|
|
3437
|
+
default_return_url = "dcim:device_list"
|
|
3438
|
+
|
|
3439
|
+
|
|
2428
3440
|
class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
|
|
2429
3441
|
parent_model = Device
|
|
2430
3442
|
parent_field = "device"
|
|
@@ -2695,7 +3707,7 @@ class InterfaceConnectionsListView(ConnectionsListView):
|
|
|
2695
3707
|
|
|
2696
3708
|
|
|
2697
3709
|
class VirtualChassisListView(generic.ObjectListView):
|
|
2698
|
-
queryset = VirtualChassis.objects.
|
|
3710
|
+
queryset = VirtualChassis.objects.all()
|
|
2699
3711
|
table = tables.VirtualChassisTable
|
|
2700
3712
|
filterset = filters.VirtualChassisFilterSet
|
|
2701
3713
|
filterset_form = forms.VirtualChassisFilterForm
|
|
@@ -2931,7 +3943,7 @@ class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
|
|
2931
3943
|
|
|
2932
3944
|
|
|
2933
3945
|
class PowerPanelListView(generic.ObjectListView):
|
|
2934
|
-
queryset = PowerPanel.objects.
|
|
3946
|
+
queryset = PowerPanel.objects.all()
|
|
2935
3947
|
filterset = filters.PowerPanelFilterSet
|
|
2936
3948
|
filterset_form = forms.PowerPanelFilterForm
|
|
2937
3949
|
table = tables.PowerPanelTable
|
|
@@ -2971,9 +3983,7 @@ class PowerPanelBulkEditView(generic.BulkEditView):
|
|
|
2971
3983
|
|
|
2972
3984
|
|
|
2973
3985
|
class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
|
2974
|
-
queryset = PowerPanel.objects.
|
|
2975
|
-
power_feed_count=count_related(PowerFeed, "power_panel")
|
|
2976
|
-
)
|
|
3986
|
+
queryset = PowerPanel.objects.all()
|
|
2977
3987
|
filterset = filters.PowerPanelFilterSet
|
|
2978
3988
|
table = tables.PowerPanelTable
|
|
2979
3989
|
|
|
@@ -3027,14 +4037,7 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3027
4037
|
filterset_class = filters.DeviceRedundancyGroupFilterSet
|
|
3028
4038
|
filterset_form_class = forms.DeviceRedundancyGroupFilterForm
|
|
3029
4039
|
form_class = forms.DeviceRedundancyGroupForm
|
|
3030
|
-
queryset = (
|
|
3031
|
-
DeviceRedundancyGroup.objects.select_related("status")
|
|
3032
|
-
.prefetch_related("controllers", "devices")
|
|
3033
|
-
.annotate(
|
|
3034
|
-
device_count=count_related(Device, "device_redundancy_group"),
|
|
3035
|
-
controller_count=count_related(Controller, "controller_device_redundancy_group"),
|
|
3036
|
-
)
|
|
3037
|
-
)
|
|
4040
|
+
queryset = DeviceRedundancyGroup.objects.all()
|
|
3038
4041
|
serializer_class = serializers.DeviceRedundancyGroupSerializer
|
|
3039
4042
|
table_class = tables.DeviceRedundancyGroupTable
|
|
3040
4043
|
|
|
@@ -3046,9 +4049,6 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3046
4049
|
devices_table = tables.DeviceTable(devices)
|
|
3047
4050
|
devices_table.columns.show("device_redundancy_group_priority")
|
|
3048
4051
|
context["devices_table"] = devices_table
|
|
3049
|
-
controllers = instance.controllers_sorted.restrict(request.user)
|
|
3050
|
-
controllers_table = tables.ControllerTable(controllers)
|
|
3051
|
-
context["controllers_table"] = controllers_table
|
|
3052
4052
|
return context
|
|
3053
4053
|
|
|
3054
4054
|
|
|
@@ -3059,11 +4059,7 @@ class InterfaceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3059
4059
|
filterset_class = filters.InterfaceRedundancyGroupFilterSet
|
|
3060
4060
|
filterset_form_class = forms.InterfaceRedundancyGroupFilterForm
|
|
3061
4061
|
form_class = forms.InterfaceRedundancyGroupForm
|
|
3062
|
-
queryset = InterfaceRedundancyGroup.objects.
|
|
3063
|
-
queryset = queryset.prefetch_related("interfaces")
|
|
3064
|
-
queryset = queryset.annotate(
|
|
3065
|
-
interface_count=count_related(Interface, "interface_redundancy_groups"),
|
|
3066
|
-
)
|
|
4062
|
+
queryset = InterfaceRedundancyGroup.objects.all()
|
|
3067
4063
|
serializer_class = serializers.InterfaceRedundancyGroupSerializer
|
|
3068
4064
|
table_class = tables.InterfaceRedundancyGroupTable
|
|
3069
4065
|
lookup_field = "pk"
|
|
@@ -3114,7 +4110,7 @@ class DeviceFamilyUIViewSet(NautobotUIViewSet):
|
|
|
3114
4110
|
filterset_form_class = forms.DeviceFamilyFilterForm
|
|
3115
4111
|
form_class = forms.DeviceFamilyForm
|
|
3116
4112
|
bulk_update_form_class = forms.DeviceFamilyBulkEditForm
|
|
3117
|
-
queryset = DeviceFamily.objects.
|
|
4113
|
+
queryset = DeviceFamily.objects.all()
|
|
3118
4114
|
serializer_class = serializers.DeviceFamilySerializer
|
|
3119
4115
|
table_class = tables.DeviceFamilyTable
|
|
3120
4116
|
lookup_field = "pk"
|
|
@@ -3157,8 +4153,7 @@ class SoftwareImageFileUIViewSet(NautobotUIViewSet):
|
|
|
3157
4153
|
filterset_form_class = forms.SoftwareImageFileFilterForm
|
|
3158
4154
|
form_class = forms.SoftwareImageFileForm
|
|
3159
4155
|
bulk_update_form_class = forms.SoftwareImageFileBulkEditForm
|
|
3160
|
-
queryset = SoftwareImageFile.objects.
|
|
3161
|
-
|
|
4156
|
+
queryset = SoftwareImageFile.objects.all()
|
|
3162
4157
|
serializer_class = serializers.SoftwareImageFileSerializer
|
|
3163
4158
|
table_class = tables.SoftwareImageFileTable
|
|
3164
4159
|
|
|
@@ -3168,11 +4163,7 @@ class SoftwareVersionUIViewSet(NautobotUIViewSet):
|
|
|
3168
4163
|
filterset_form_class = forms.SoftwareVersionFilterForm
|
|
3169
4164
|
form_class = forms.SoftwareVersionForm
|
|
3170
4165
|
bulk_update_form_class = forms.SoftwareVersionBulkEditForm
|
|
3171
|
-
queryset = SoftwareVersion.objects.
|
|
3172
|
-
software_image_file_count=count_related(SoftwareImageFile, "software_version"),
|
|
3173
|
-
device_count=count_related(Device, "software_version"),
|
|
3174
|
-
inventory_item_count=count_related(InventoryItem, "software_version"),
|
|
3175
|
-
)
|
|
4166
|
+
queryset = SoftwareVersion.objects.all()
|
|
3176
4167
|
serializer_class = serializers.SoftwareVersionSerializer
|
|
3177
4168
|
table_class = tables.SoftwareVersionTable
|
|
3178
4169
|
|
|
@@ -3215,11 +4206,7 @@ class ControllerManagedDeviceGroupUIViewSet(NautobotUIViewSet):
|
|
|
3215
4206
|
filterset_form_class = forms.ControllerManagedDeviceGroupFilterForm
|
|
3216
4207
|
form_class = forms.ControllerManagedDeviceGroupForm
|
|
3217
4208
|
bulk_update_form_class = forms.ControllerManagedDeviceGroupBulkEditForm
|
|
3218
|
-
queryset = (
|
|
3219
|
-
ControllerManagedDeviceGroup.objects.all()
|
|
3220
|
-
.prefetch_related("devices")
|
|
3221
|
-
.annotate(device_count=count_related(Device, "controller_managed_device_group"))
|
|
3222
|
-
)
|
|
4209
|
+
queryset = ControllerManagedDeviceGroup.objects.all()
|
|
3223
4210
|
serializer_class = serializers.ControllerManagedDeviceGroupSerializer
|
|
3224
4211
|
table_class = tables.ControllerManagedDeviceGroupTable
|
|
3225
4212
|
template_name = "dcim/controllermanageddevicegroup_create.html"
|