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/extras/views.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
1
2
|
import logging
|
|
3
|
+
from urllib.parse import parse_qs
|
|
2
4
|
|
|
3
5
|
from celery import chain
|
|
4
6
|
from django.contrib import messages
|
|
7
|
+
from django.contrib.auth.models import AnonymousUser
|
|
5
8
|
from django.contrib.contenttypes.models import ContentType
|
|
6
9
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
7
|
-
from django.db import transaction
|
|
10
|
+
from django.db import IntegrityError, transaction
|
|
8
11
|
from django.db.models import ProtectedError, Q
|
|
9
12
|
from django.forms.utils import pretty_name
|
|
10
13
|
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|
11
14
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
12
15
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
13
16
|
from django.urls import reverse
|
|
17
|
+
from django.urls.exceptions import NoReverseMatch
|
|
14
18
|
from django.utils import timezone
|
|
15
19
|
from django.utils.encoding import iri_to_uri
|
|
16
20
|
from django.utils.html import format_html
|
|
@@ -18,38 +22,51 @@ from django.utils.http import url_has_allowed_host_and_scheme
|
|
|
18
22
|
from django.views.generic import View
|
|
19
23
|
from django_tables2 import RequestConfig
|
|
20
24
|
from jsonschema.validators import Draft7Validator
|
|
25
|
+
from rest_framework.decorators import action
|
|
21
26
|
|
|
22
27
|
from nautobot.core.forms import restrict_form_fields
|
|
23
28
|
from nautobot.core.models.querysets import count_related
|
|
24
29
|
from nautobot.core.models.utils import pretty_print_query
|
|
25
30
|
from nautobot.core.tables import ButtonsColumn
|
|
26
|
-
from nautobot.core.utils.
|
|
31
|
+
from nautobot.core.utils.config import get_settings_or_config
|
|
32
|
+
from nautobot.core.utils.lookup import (
|
|
33
|
+
get_filterset_for_model,
|
|
34
|
+
get_route_for_model,
|
|
35
|
+
get_table_class_string_from_view_name,
|
|
36
|
+
get_table_for_model,
|
|
37
|
+
)
|
|
38
|
+
from nautobot.core.utils.permissions import get_permission_for_model
|
|
27
39
|
from nautobot.core.utils.requests import normalize_querydict
|
|
28
40
|
from nautobot.core.views import generic, viewsets
|
|
29
41
|
from nautobot.core.views.mixins import (
|
|
42
|
+
GetReturnURLMixin,
|
|
30
43
|
ObjectBulkDestroyViewMixin,
|
|
31
44
|
ObjectBulkUpdateViewMixin,
|
|
45
|
+
ObjectChangeLogViewMixin,
|
|
32
46
|
ObjectDestroyViewMixin,
|
|
47
|
+
ObjectDetailViewMixin,
|
|
33
48
|
ObjectEditViewMixin,
|
|
49
|
+
ObjectListViewMixin,
|
|
34
50
|
ObjectPermissionRequiredMixin,
|
|
35
51
|
)
|
|
36
52
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
37
53
|
from nautobot.core.views.utils import prepare_cloned_fields
|
|
38
54
|
from nautobot.core.views.viewsets import NautobotUIViewSet
|
|
39
|
-
from nautobot.dcim.models import Controller, Device, Interface,
|
|
40
|
-
from nautobot.dcim.tables import ControllerTable, DeviceTable, RackTable
|
|
55
|
+
from nautobot.dcim.models import Controller, Device, Interface, Module, Rack
|
|
56
|
+
from nautobot.dcim.tables import ControllerTable, DeviceTable, InterfaceTable, ModuleTable, RackTable
|
|
41
57
|
from nautobot.extras.constants import JOB_OVERRIDABLE_FIELDS
|
|
58
|
+
from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
|
|
42
59
|
from nautobot.extras.signals import change_context_state
|
|
43
60
|
from nautobot.extras.tasks import delete_custom_field_data
|
|
44
61
|
from nautobot.extras.utils import get_base_template, get_worker_count
|
|
45
62
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
46
63
|
from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
|
|
47
64
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
48
|
-
from nautobot.virtualization.tables import VirtualMachineTable
|
|
65
|
+
from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
|
|
49
66
|
|
|
50
67
|
from . import filters, forms, tables
|
|
51
68
|
from .api import serializers
|
|
52
|
-
from .choices import JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
69
|
+
from .choices import DynamicGroupTypeChoices, JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
53
70
|
from .datasources import (
|
|
54
71
|
enqueue_git_repository_diff_origin_and_local,
|
|
55
72
|
enqueue_pull_git_repository_and_refresh_data,
|
|
@@ -75,19 +92,24 @@ from .models import (
|
|
|
75
92
|
JobHook,
|
|
76
93
|
JobLogEntry,
|
|
77
94
|
JobResult,
|
|
95
|
+
MetadataType,
|
|
78
96
|
Note,
|
|
79
97
|
ObjectChange,
|
|
98
|
+
ObjectMetadata,
|
|
80
99
|
Relationship,
|
|
81
100
|
RelationshipAssociation,
|
|
82
101
|
Role,
|
|
102
|
+
SavedView,
|
|
83
103
|
ScheduledJob,
|
|
84
104
|
Secret,
|
|
85
105
|
SecretsGroup,
|
|
86
106
|
SecretsGroupAssociation,
|
|
107
|
+
StaticGroupAssociation,
|
|
87
108
|
Status,
|
|
88
109
|
Tag,
|
|
89
110
|
TaggedItem,
|
|
90
111
|
Team,
|
|
112
|
+
UserSavedViewAssociation,
|
|
91
113
|
Webhook,
|
|
92
114
|
)
|
|
93
115
|
from .registry import registry
|
|
@@ -698,12 +720,19 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
698
720
|
|
|
699
721
|
def get_extra_context(self, request, instance):
|
|
700
722
|
context = super().get_extra_context(request, instance)
|
|
701
|
-
model = instance.
|
|
723
|
+
model = instance.model
|
|
702
724
|
table_class = get_table_for_model(model)
|
|
703
725
|
|
|
726
|
+
if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
|
|
727
|
+
# Ensure that members cache is up-to-date for this specific group
|
|
728
|
+
members = instance.update_cached_members()
|
|
729
|
+
messages.success(request, f"Refreshed cached members list for {instance}")
|
|
730
|
+
else:
|
|
731
|
+
members = instance.members
|
|
732
|
+
|
|
704
733
|
if table_class is not None:
|
|
705
734
|
# Members table (for display on Members nav tab)
|
|
706
|
-
members_table = table_class(
|
|
735
|
+
members_table = table_class(members.restrict(request.user, "view"), orderable=False)
|
|
707
736
|
paginate = {
|
|
708
737
|
"paginator_class": EnhancedPaginator,
|
|
709
738
|
"per_page": get_paginate_count(request),
|
|
@@ -723,7 +752,16 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
723
752
|
ancestors_table = tables.NestedDynamicGroupAncestorsTable(ancestors, orderable=False)
|
|
724
753
|
ancestors_tree = instance.flatten_ancestors_tree(instance.ancestors_tree())
|
|
725
754
|
|
|
726
|
-
|
|
755
|
+
if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
|
|
756
|
+
context["raw_query"] = pretty_print_query(instance.generate_query())
|
|
757
|
+
context["members_list_url"] = None
|
|
758
|
+
else:
|
|
759
|
+
context["raw_query"] = None
|
|
760
|
+
try:
|
|
761
|
+
context["members_list_url"] = reverse(get_route_for_model(instance.model, "list"))
|
|
762
|
+
except NoReverseMatch:
|
|
763
|
+
context["members_list_url"] = None
|
|
764
|
+
context["members_verbose_name_plural"] = instance.model._meta.verbose_name_plural
|
|
727
765
|
context["members_table"] = members_table
|
|
728
766
|
context["ancestors_table"] = ancestors_table
|
|
729
767
|
context["ancestors_tree"] = ancestors_tree
|
|
@@ -775,16 +813,19 @@ class DynamicGroupEditView(generic.ObjectEditView):
|
|
|
775
813
|
# Obtain the instance, but do not yet `save()` it to the database.
|
|
776
814
|
obj = form.save(commit=False)
|
|
777
815
|
|
|
778
|
-
# Process the filter form and save the query filters to `obj.filter`.
|
|
779
816
|
ctx = self.get_extra_context(request, obj)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
817
|
+
if obj.group_type == DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER:
|
|
818
|
+
# Process the filter form and save the query filters to `obj.filter`.
|
|
819
|
+
filter_form = ctx["filter_form"]
|
|
820
|
+
if filter_form.is_valid():
|
|
821
|
+
obj.set_filter(filter_form.cleaned_data)
|
|
822
|
+
else:
|
|
823
|
+
raise RuntimeError(filter_form.errors)
|
|
785
824
|
|
|
786
825
|
# After filters have been set, now we save the object to the database.
|
|
787
826
|
obj.save()
|
|
827
|
+
# Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
|
|
828
|
+
form.save_m2m()
|
|
788
829
|
# Check that the new object conforms with any assigned object-level permissions
|
|
789
830
|
self.queryset.get(pk=obj.pk)
|
|
790
831
|
|
|
@@ -867,6 +908,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
867
908
|
filterset = filters.DynamicGroupFilterSet
|
|
868
909
|
|
|
869
910
|
|
|
911
|
+
# 3.0 TODO: remove, deprecated since 2.3 (#5845)
|
|
870
912
|
class ObjectDynamicGroupsView(generic.GenericView):
|
|
871
913
|
"""
|
|
872
914
|
Present a list of dynamic groups associated to a particular object.
|
|
@@ -883,16 +925,18 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
883
925
|
obj = get_object_or_404(model, **kwargs)
|
|
884
926
|
|
|
885
927
|
# Gather all dynamic groups for this object (and its related objects)
|
|
886
|
-
|
|
887
|
-
data=obj.
|
|
928
|
+
dynamicgroups_table = tables.DynamicGroupTable(
|
|
929
|
+
data=obj.dynamic_groups.restrict(request.user, "view"), orderable=False
|
|
888
930
|
)
|
|
931
|
+
dynamicgroups_table.columns.hide("content_type")
|
|
932
|
+
dynamicgroups_table.columns.hide("members")
|
|
889
933
|
|
|
890
934
|
# Apply the request context
|
|
891
935
|
paginate = {
|
|
892
936
|
"paginator_class": EnhancedPaginator,
|
|
893
937
|
"per_page": get_paginate_count(request),
|
|
894
938
|
}
|
|
895
|
-
RequestConfig(request, paginate).configure(
|
|
939
|
+
RequestConfig(request, paginate).configure(dynamicgroups_table)
|
|
896
940
|
|
|
897
941
|
self.base_template = get_base_template(self.base_template, model)
|
|
898
942
|
|
|
@@ -903,7 +947,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
903
947
|
"object": obj,
|
|
904
948
|
"verbose_name": obj._meta.verbose_name,
|
|
905
949
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
906
|
-
"table":
|
|
950
|
+
"table": dynamicgroups_table,
|
|
907
951
|
"base_template": self.base_template,
|
|
908
952
|
"active_tab": "dynamic-groups",
|
|
909
953
|
},
|
|
@@ -1344,25 +1388,55 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
1344
1388
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
1345
1389
|
|
|
1346
1390
|
if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1391
|
+
crontab = ""
|
|
1392
|
+
|
|
1393
|
+
if schedule_type == JobExecutionType.TYPE_IMMEDIATELY:
|
|
1394
|
+
# The job must be approved.
|
|
1395
|
+
# If the schedule_type is immediate, we still create the task, but mark it for approval
|
|
1396
|
+
# as a once in the future task with the due date set to the current time. This means
|
|
1397
|
+
# when approval is granted, the task is immediately due for execution.
|
|
1398
|
+
schedule_type = JobExecutionType.TYPE_FUTURE
|
|
1399
|
+
schedule_datetime = timezone.now()
|
|
1400
|
+
schedule_name = f"{job_model} - {schedule_datetime}"
|
|
1401
|
+
|
|
1402
|
+
else:
|
|
1403
|
+
schedule_name = schedule_form.cleaned_data["_schedule_name"]
|
|
1404
|
+
|
|
1405
|
+
if schedule_type == JobExecutionType.TYPE_CUSTOM:
|
|
1406
|
+
crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
|
|
1407
|
+
# doing .get("key", "default") returns None instead of "default" here for some reason
|
|
1408
|
+
schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
|
|
1409
|
+
if schedule_datetime is None:
|
|
1410
|
+
# "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
|
|
1411
|
+
# which returns timezone.now() + timedelta(seconds=15)
|
|
1412
|
+
schedule_datetime = timezone.now() + timedelta(seconds=20)
|
|
1413
|
+
else:
|
|
1414
|
+
schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
|
|
1415
|
+
|
|
1416
|
+
celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
|
|
1417
|
+
scheduled_job = ScheduledJob(
|
|
1418
|
+
name=schedule_name,
|
|
1419
|
+
task=job_model.class_path,
|
|
1420
|
+
job_model=job_model,
|
|
1421
|
+
start_time=schedule_datetime,
|
|
1422
|
+
description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
|
|
1423
|
+
kwargs=job_class.serialize_data(job_form.cleaned_data),
|
|
1424
|
+
celery_kwargs=celery_kwargs,
|
|
1352
1425
|
interval=schedule_type,
|
|
1353
|
-
|
|
1426
|
+
one_off=schedule_type == JobExecutionType.TYPE_FUTURE,
|
|
1427
|
+
queue=task_queue,
|
|
1428
|
+
user=request.user,
|
|
1354
1429
|
approval_required=job_model.approval_required,
|
|
1355
|
-
|
|
1356
|
-
profile=profile,
|
|
1357
|
-
**job_class.serialize_data(job_form.cleaned_data),
|
|
1430
|
+
crontab=crontab,
|
|
1358
1431
|
)
|
|
1432
|
+
scheduled_job.validated_save()
|
|
1359
1433
|
|
|
1360
1434
|
if job_model.approval_required:
|
|
1361
|
-
messages.success(request, f"Job {
|
|
1362
|
-
return redirect(return_url
|
|
1435
|
+
messages.success(request, f"Job {schedule_name} successfully submitted for approval")
|
|
1436
|
+
return redirect(return_url if return_url else "extras:scheduledjob_approval_queue_list")
|
|
1363
1437
|
else:
|
|
1364
|
-
messages.success(request, f"Job {
|
|
1365
|
-
return redirect(return_url
|
|
1438
|
+
messages.success(request, f"Job {schedule_name} successfully scheduled")
|
|
1439
|
+
return redirect(return_url if return_url else "extras:scheduledjob_list")
|
|
1366
1440
|
|
|
1367
1441
|
else:
|
|
1368
1442
|
# Enqueue job for immediate execution
|
|
@@ -1583,6 +1657,252 @@ class JobApprovalRequestView(generic.ObjectView):
|
|
|
1583
1657
|
)
|
|
1584
1658
|
|
|
1585
1659
|
|
|
1660
|
+
#
|
|
1661
|
+
# Saved Views
|
|
1662
|
+
#
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
class SavedViewUIViewSet(
|
|
1666
|
+
ObjectDetailViewMixin,
|
|
1667
|
+
ObjectChangeLogViewMixin,
|
|
1668
|
+
ObjectDestroyViewMixin,
|
|
1669
|
+
ObjectEditViewMixin,
|
|
1670
|
+
ObjectListViewMixin,
|
|
1671
|
+
):
|
|
1672
|
+
queryset = SavedView.objects.all()
|
|
1673
|
+
form_class = forms.SavedViewForm
|
|
1674
|
+
filterset_class = filters.SavedViewFilterSet
|
|
1675
|
+
serializer_class = serializers.SavedViewSerializer
|
|
1676
|
+
table_class = tables.SavedViewTable
|
|
1677
|
+
action_buttons = ("export",)
|
|
1678
|
+
|
|
1679
|
+
def alter_queryset(self, request):
|
|
1680
|
+
"""
|
|
1681
|
+
Two scenarios we need to handle here:
|
|
1682
|
+
1. User can view all saved views with extras.view_savedview permission.
|
|
1683
|
+
2. User without the permission can only view shared savedviews and his/her own saved views.
|
|
1684
|
+
"""
|
|
1685
|
+
queryset = super().alter_queryset(request)
|
|
1686
|
+
user = request.user
|
|
1687
|
+
if user.has_perms(["extras.view_savedview"]):
|
|
1688
|
+
saved_views = queryset.restrict(user, "view")
|
|
1689
|
+
else:
|
|
1690
|
+
shared_saved_views = queryset.filter(is_shared=True)
|
|
1691
|
+
user_owned_saved_views = queryset.filter(owner=user)
|
|
1692
|
+
saved_views = shared_saved_views | user_owned_saved_views
|
|
1693
|
+
return saved_views
|
|
1694
|
+
|
|
1695
|
+
def get_queryset(self):
|
|
1696
|
+
"""
|
|
1697
|
+
Get the list of items for this view.
|
|
1698
|
+
All users should be able to see saved views so we do not apply extra permissions.
|
|
1699
|
+
"""
|
|
1700
|
+
return self.queryset.all()
|
|
1701
|
+
|
|
1702
|
+
def check_permissions(self, request):
|
|
1703
|
+
"""
|
|
1704
|
+
Override this method to not check any permissions.
|
|
1705
|
+
Since users with <app_label>.view_<model_name> permissions should be able to view saved views related to this model.
|
|
1706
|
+
And those permissions will be enforced in the related view.
|
|
1707
|
+
"""
|
|
1708
|
+
|
|
1709
|
+
def dispatch(self, request, *args, **kwargs):
|
|
1710
|
+
if isinstance(request.user, AnonymousUser):
|
|
1711
|
+
return self.handle_no_permission()
|
|
1712
|
+
return super().dispatch(request, *args, **kwargs)
|
|
1713
|
+
|
|
1714
|
+
def extra_message_context(self, obj):
|
|
1715
|
+
"""
|
|
1716
|
+
Context variables for this extra message.
|
|
1717
|
+
"""
|
|
1718
|
+
return {"new_global_default_view": obj}
|
|
1719
|
+
|
|
1720
|
+
def extra_message(self, **kwargs):
|
|
1721
|
+
new_global_default_view = kwargs.get("new_global_default_view")
|
|
1722
|
+
view_name = new_global_default_view.view
|
|
1723
|
+
message = ""
|
|
1724
|
+
if new_global_default_view.is_global_default:
|
|
1725
|
+
message += f"<br>The global default saved View for '{view_name}' is set to <a href='{new_global_default_view.get_absolute_url()}'>{new_global_default_view.name}</a>."
|
|
1726
|
+
return message
|
|
1727
|
+
|
|
1728
|
+
def list(self, request, *args, **kwargs):
|
|
1729
|
+
if not request.user.has_perms(["extras.view_savedview"]):
|
|
1730
|
+
return self.handle_no_permission()
|
|
1731
|
+
return super().list(request, *args, **kwargs)
|
|
1732
|
+
|
|
1733
|
+
def retrieve(self, request, *args, **kwargs):
|
|
1734
|
+
"""
|
|
1735
|
+
The detail view for a saved view should the related ObjectListView with saved configurations applied
|
|
1736
|
+
"""
|
|
1737
|
+
instance = self.get_object()
|
|
1738
|
+
list_view_url = reverse(instance.view) + f"?saved_view={instance.pk}"
|
|
1739
|
+
return redirect(list_view_url)
|
|
1740
|
+
|
|
1741
|
+
@action(detail=True, name="Set Default", methods=["get"], url_path="set-default", url_name="set_default")
|
|
1742
|
+
def set_default(self, request, *args, **kwargs):
|
|
1743
|
+
"""
|
|
1744
|
+
Set current saved view as the the request.user default view. Overriding the global default view if there is one.
|
|
1745
|
+
"""
|
|
1746
|
+
user = request.user
|
|
1747
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1748
|
+
UserSavedViewAssociation.objects.filter(user=user, view_name=sv.view).delete()
|
|
1749
|
+
UserSavedViewAssociation.objects.create(user=user, saved_view=sv, view_name=sv.view)
|
|
1750
|
+
list_view_url = sv.get_absolute_url()
|
|
1751
|
+
messages.success(
|
|
1752
|
+
request, f"Successfully set current view '{sv.name}' as the default '{sv.view}' view for user {user}"
|
|
1753
|
+
)
|
|
1754
|
+
return redirect(list_view_url)
|
|
1755
|
+
|
|
1756
|
+
@action(detail=True, name="Update Config", methods=["get"], url_path="update-config", url_name="update_config")
|
|
1757
|
+
def update_saved_view_config(self, request, *args, **kwargs):
|
|
1758
|
+
"""
|
|
1759
|
+
Extract filter_params, pagination and sort_order from request.GET and apply it to the SavedView specified
|
|
1760
|
+
"""
|
|
1761
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1762
|
+
if sv.owner == request.user or request.user.has_perms(["extras.change_savedview"]):
|
|
1763
|
+
pass
|
|
1764
|
+
else:
|
|
1765
|
+
messages.error(
|
|
1766
|
+
request, f"You do not have the required permission to modify this Saved View owned by {sv.owner}"
|
|
1767
|
+
)
|
|
1768
|
+
return redirect(self.get_return_url(request, obj=sv))
|
|
1769
|
+
table_changes_pending = request.GET.get("table_changes_pending", False)
|
|
1770
|
+
all_filters_removed = request.GET.get("all_filters_removed", False)
|
|
1771
|
+
pagination_count = request.GET.get("per_page", None)
|
|
1772
|
+
if pagination_count is not None:
|
|
1773
|
+
sv.config["pagination_count"] = int(pagination_count)
|
|
1774
|
+
sort_order = request.GET.getlist("sort", [])
|
|
1775
|
+
if sort_order:
|
|
1776
|
+
sv.config["sort_order"] = sort_order
|
|
1777
|
+
|
|
1778
|
+
filter_params = {}
|
|
1779
|
+
for key in request.GET:
|
|
1780
|
+
if key in self.non_filter_params:
|
|
1781
|
+
continue
|
|
1782
|
+
# TODO: this is fragile, other single-value filters will also be unhappy if given a list
|
|
1783
|
+
if key == "q":
|
|
1784
|
+
filter_params[key] = request.GET.get(key)
|
|
1785
|
+
else:
|
|
1786
|
+
filter_params[key] = request.GET.getlist(key)
|
|
1787
|
+
|
|
1788
|
+
if filter_params:
|
|
1789
|
+
sv.config["filter_params"] = filter_params
|
|
1790
|
+
elif all_filters_removed:
|
|
1791
|
+
sv.config["filter_params"] = {}
|
|
1792
|
+
|
|
1793
|
+
if table_changes_pending:
|
|
1794
|
+
table_class = get_table_class_string_from_view_name(sv.view)
|
|
1795
|
+
if table_class:
|
|
1796
|
+
if sv.config.get("table_config", None) is None:
|
|
1797
|
+
sv.config["table_config"] = {}
|
|
1798
|
+
sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
|
|
1799
|
+
|
|
1800
|
+
sv.validated_save()
|
|
1801
|
+
list_view_url = sv.get_absolute_url()
|
|
1802
|
+
messages.success(request, f"Successfully updated current view {sv.name}")
|
|
1803
|
+
return redirect(list_view_url)
|
|
1804
|
+
|
|
1805
|
+
def create(self, request, *args, **kwargs):
|
|
1806
|
+
"""
|
|
1807
|
+
This method will extract filter_params, pagination and sort_order from request.GET
|
|
1808
|
+
and the name of the new SavedView from request.POST to create a new SavedView.
|
|
1809
|
+
"""
|
|
1810
|
+
name = request.POST.get("name")
|
|
1811
|
+
is_shared = request.POST.get("is_shared", False)
|
|
1812
|
+
if is_shared:
|
|
1813
|
+
is_shared = True
|
|
1814
|
+
params = request.POST.get("params", "")
|
|
1815
|
+
|
|
1816
|
+
param_dict = parse_qs(params)
|
|
1817
|
+
|
|
1818
|
+
single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "q", "per_page"]
|
|
1819
|
+
for key in param_dict.keys():
|
|
1820
|
+
if key in single_value_params:
|
|
1821
|
+
param_dict[key] = param_dict[key][0]
|
|
1822
|
+
|
|
1823
|
+
derived_view_pk = param_dict.get("saved_view", None)
|
|
1824
|
+
derived_instance = None
|
|
1825
|
+
if derived_view_pk:
|
|
1826
|
+
derived_instance = self.get_queryset().get(pk=derived_view_pk)
|
|
1827
|
+
view_name = request.POST.get("view")
|
|
1828
|
+
try:
|
|
1829
|
+
reverse(view_name)
|
|
1830
|
+
except NoReverseMatch:
|
|
1831
|
+
messages.error(request, f"Invalid view name {view_name} specified.")
|
|
1832
|
+
if derived_view_pk:
|
|
1833
|
+
return redirect(self.get_return_url(request, obj=derived_instance))
|
|
1834
|
+
else:
|
|
1835
|
+
return redirect(self.get_return_url(request))
|
|
1836
|
+
table_changes_pending = param_dict.get("table_changes_pending", False)
|
|
1837
|
+
all_filters_removed = param_dict.get("all_filters_removed", False)
|
|
1838
|
+
try:
|
|
1839
|
+
sv = SavedView.objects.create(name=name, owner=request.user, view=view_name, is_shared=is_shared)
|
|
1840
|
+
except IntegrityError:
|
|
1841
|
+
messages.error(request, f"You already have a Saved View named '{name}' for this view '{view_name}'")
|
|
1842
|
+
if derived_view_pk:
|
|
1843
|
+
return redirect(self.get_return_url(request, obj=derived_instance))
|
|
1844
|
+
else:
|
|
1845
|
+
return redirect(reverse(view_name))
|
|
1846
|
+
pagination_count = param_dict.get("per_page", None)
|
|
1847
|
+
if not pagination_count:
|
|
1848
|
+
if derived_instance and derived_instance.config.get("pagination_count", None):
|
|
1849
|
+
pagination_count = derived_instance.config["pagination_count"]
|
|
1850
|
+
else:
|
|
1851
|
+
pagination_count = get_settings_or_config("PAGINATE_COUNT")
|
|
1852
|
+
sv.config["pagination_count"] = int(pagination_count)
|
|
1853
|
+
sort_order = param_dict.get("sort", [])
|
|
1854
|
+
if not sort_order:
|
|
1855
|
+
if derived_instance:
|
|
1856
|
+
sort_order = derived_instance.config.get("sort_order", [])
|
|
1857
|
+
sv.config["sort_order"] = sort_order
|
|
1858
|
+
|
|
1859
|
+
sv.config["filter_params"] = {}
|
|
1860
|
+
for key in param_dict:
|
|
1861
|
+
if key in [*self.non_filter_params, "view"]:
|
|
1862
|
+
continue
|
|
1863
|
+
sv.config["filter_params"][key] = param_dict.get(key)
|
|
1864
|
+
if not sv.config["filter_params"]:
|
|
1865
|
+
if derived_instance and all_filters_removed:
|
|
1866
|
+
sv.config["filter_params"] = {}
|
|
1867
|
+
elif derived_instance:
|
|
1868
|
+
sv.config["filter_params"] = derived_instance.config["filter_params"]
|
|
1869
|
+
|
|
1870
|
+
table_class = get_table_class_string_from_view_name(view_name)
|
|
1871
|
+
sv.config["table_config"] = {}
|
|
1872
|
+
if table_class:
|
|
1873
|
+
if table_changes_pending or derived_instance is None:
|
|
1874
|
+
sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
|
|
1875
|
+
elif derived_instance.config.get("table_config") and derived_instance.config["table_config"].get(
|
|
1876
|
+
f"{table_class}"
|
|
1877
|
+
):
|
|
1878
|
+
sv.config["table_config"][f"{table_class}"] = derived_instance.config["table_config"][f"{table_class}"]
|
|
1879
|
+
try:
|
|
1880
|
+
sv.validated_save()
|
|
1881
|
+
list_view_url = sv.get_absolute_url()
|
|
1882
|
+
message = f"Successfully created new Saved View '{sv.name}'."
|
|
1883
|
+
messages.success(request, message)
|
|
1884
|
+
return redirect(list_view_url)
|
|
1885
|
+
except ValidationError as e:
|
|
1886
|
+
messages.error(request, e)
|
|
1887
|
+
return redirect(self.get_return_url(request))
|
|
1888
|
+
|
|
1889
|
+
def destroy(self, request, *args, **kwargs):
|
|
1890
|
+
"""
|
|
1891
|
+
request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response.
|
|
1892
|
+
request.POST: call perform_destroy() which validates the form and perform the action of delete.
|
|
1893
|
+
Override to add more variables to Response
|
|
1894
|
+
"""
|
|
1895
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1896
|
+
if sv.owner == request.user or request.user.has_perms(["extras.delete_savedview"]):
|
|
1897
|
+
pass
|
|
1898
|
+
else:
|
|
1899
|
+
messages.error(
|
|
1900
|
+
request, f"You do not have the required permission to delete this Saved View owned by {sv.owner}"
|
|
1901
|
+
)
|
|
1902
|
+
return redirect(self.get_return_url(request, obj=sv))
|
|
1903
|
+
return super().destroy(request, *args, **kwargs)
|
|
1904
|
+
|
|
1905
|
+
|
|
1586
1906
|
class ScheduledJobListView(generic.ObjectListView):
|
|
1587
1907
|
queryset = ScheduledJob.objects.enabled()
|
|
1588
1908
|
table = tables.ScheduledJobTable
|
|
@@ -1756,13 +2076,8 @@ class JobLogEntryTableView(generic.GenericView):
|
|
|
1756
2076
|
else:
|
|
1757
2077
|
queryset = instance.job_log_entries.all()
|
|
1758
2078
|
log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
"per_page": get_paginate_count(request),
|
|
1762
|
-
}
|
|
1763
|
-
RequestConfig(request, paginate).configure(log_table)
|
|
1764
|
-
table = log_table.as_html(request)
|
|
1765
|
-
return HttpResponse(table)
|
|
2079
|
+
RequestConfig(request).configure(log_table)
|
|
2080
|
+
return HttpResponse(log_table.as_html(request))
|
|
1766
2081
|
|
|
1767
2082
|
|
|
1768
2083
|
#
|
|
@@ -1882,6 +2197,60 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
1882
2197
|
)
|
|
1883
2198
|
|
|
1884
2199
|
|
|
2200
|
+
#
|
|
2201
|
+
# Metadata
|
|
2202
|
+
#
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
class MetadataTypeUIViewSet(NautobotUIViewSet):
|
|
2206
|
+
bulk_update_form_class = forms.MetadataTypeBulkEditForm
|
|
2207
|
+
filterset_class = filters.MetadataTypeFilterSet
|
|
2208
|
+
filterset_form_class = forms.MetadataTypeFilterForm
|
|
2209
|
+
form_class = forms.MetadataTypeForm
|
|
2210
|
+
queryset = MetadataType.objects.all()
|
|
2211
|
+
serializer_class = serializers.MetadataTypeSerializer
|
|
2212
|
+
table_class = tables.MetadataTypeTable
|
|
2213
|
+
|
|
2214
|
+
def get_extra_context(self, request, instance):
|
|
2215
|
+
context = super().get_extra_context(request, instance)
|
|
2216
|
+
|
|
2217
|
+
if self.action in ("create", "update"):
|
|
2218
|
+
if request.POST:
|
|
2219
|
+
context["choices"] = forms.MetadataChoiceFormSet(data=request.POST, instance=instance)
|
|
2220
|
+
else:
|
|
2221
|
+
context["choices"] = forms.MetadataChoiceFormSet(instance=instance)
|
|
2222
|
+
|
|
2223
|
+
return context
|
|
2224
|
+
|
|
2225
|
+
def form_save(self, form, **kwargs):
|
|
2226
|
+
obj = super().form_save(form, **kwargs)
|
|
2227
|
+
|
|
2228
|
+
# Process the formset for choices
|
|
2229
|
+
ctx = self.get_extra_context(self.request, obj)
|
|
2230
|
+
choices = ctx["choices"]
|
|
2231
|
+
if choices.is_valid():
|
|
2232
|
+
choices.save()
|
|
2233
|
+
else:
|
|
2234
|
+
raise ValidationError(choices.errors)
|
|
2235
|
+
|
|
2236
|
+
return obj
|
|
2237
|
+
|
|
2238
|
+
|
|
2239
|
+
class ObjectMetadataUIViewSet(
|
|
2240
|
+
ObjectBulkDestroyViewMixin,
|
|
2241
|
+
ObjectChangeLogViewMixin,
|
|
2242
|
+
ObjectDestroyViewMixin,
|
|
2243
|
+
ObjectDetailViewMixin,
|
|
2244
|
+
ObjectListViewMixin,
|
|
2245
|
+
):
|
|
2246
|
+
filterset_class = filters.ObjectMetadataFilterSet
|
|
2247
|
+
filterset_form_class = forms.ObjectMetadataFilterForm
|
|
2248
|
+
queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "scoped_fields")
|
|
2249
|
+
serializer_class = serializers.ObjectMetadataSerializer
|
|
2250
|
+
table_class = tables.ObjectMetadataTable
|
|
2251
|
+
action_buttons = ("export",)
|
|
2252
|
+
|
|
2253
|
+
|
|
1885
2254
|
#
|
|
1886
2255
|
# Notes
|
|
1887
2256
|
#
|
|
@@ -2041,43 +2410,32 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2041
2410
|
}
|
|
2042
2411
|
|
|
2043
2412
|
if ContentType.objects.get_for_model(Device) in context["content_types"]:
|
|
2044
|
-
devices = instance.devices.
|
|
2045
|
-
"status",
|
|
2046
|
-
"location",
|
|
2047
|
-
"tenant",
|
|
2048
|
-
"role",
|
|
2049
|
-
"rack",
|
|
2050
|
-
"device_type",
|
|
2051
|
-
).restrict(request.user, "view")
|
|
2413
|
+
devices = instance.devices.restrict(request.user, "view")
|
|
2052
2414
|
device_table = DeviceTable(devices)
|
|
2053
2415
|
device_table.columns.hide("role")
|
|
2054
2416
|
RequestConfig(request, paginate).configure(device_table)
|
|
2055
2417
|
context["device_table"] = device_table
|
|
2056
2418
|
|
|
2419
|
+
if ContentType.objects.get_for_model(Interface) in context["content_types"]:
|
|
2420
|
+
interfaces = instance.interfaces.restrict(request.user, "view")
|
|
2421
|
+
interface_table = InterfaceTable(interfaces)
|
|
2422
|
+
interface_table.columns.hide("role")
|
|
2423
|
+
RequestConfig(request, paginate).configure(interface_table)
|
|
2424
|
+
context["interface_table"] = interface_table
|
|
2425
|
+
|
|
2057
2426
|
if ContentType.objects.get_for_model(Controller) in context["content_types"]:
|
|
2058
|
-
controllers = instance.controllers.
|
|
2059
|
-
"status",
|
|
2060
|
-
"location",
|
|
2061
|
-
"tenant",
|
|
2062
|
-
"role",
|
|
2063
|
-
).restrict(request.user, "view")
|
|
2427
|
+
controllers = instance.controllers.restrict(request.user, "view")
|
|
2064
2428
|
controller_table = ControllerTable(controllers)
|
|
2065
2429
|
controller_table.columns.hide("role")
|
|
2066
2430
|
RequestConfig(request, paginate).configure(controller_table)
|
|
2067
2431
|
context["controller_table"] = controller_table
|
|
2068
2432
|
|
|
2069
2433
|
if ContentType.objects.get_for_model(IPAddress) in context["content_types"]:
|
|
2070
|
-
ipaddress = (
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
|
|
2076
|
-
vm_interface_count=count_related(VMInterface, "ip_addresses"),
|
|
2077
|
-
vm_interface_parent_count=count_related(
|
|
2078
|
-
VirtualMachine, "interfaces__ip_addresses", distinct=True
|
|
2079
|
-
),
|
|
2080
|
-
)
|
|
2434
|
+
ipaddress = instance.ip_addresses.restrict(request.user, "view").annotate(
|
|
2435
|
+
interface_count=count_related(Interface, "ip_addresses"),
|
|
2436
|
+
interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
|
|
2437
|
+
vm_interface_count=count_related(VMInterface, "ip_addresses"),
|
|
2438
|
+
vm_interface_parent_count=count_related(VirtualMachine, "interfaces__ip_addresses", distinct=True),
|
|
2081
2439
|
)
|
|
2082
2440
|
ipaddress_table = IPAddressTable(ipaddress)
|
|
2083
2441
|
ipaddress_table.columns.hide("role")
|
|
@@ -2085,57 +2443,41 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2085
2443
|
context["ipaddress_table"] = ipaddress_table
|
|
2086
2444
|
|
|
2087
2445
|
if ContentType.objects.get_for_model(Prefix) in context["content_types"]:
|
|
2088
|
-
prefixes = (
|
|
2089
|
-
instance.prefixes.select_related(
|
|
2090
|
-
"status",
|
|
2091
|
-
"tenant",
|
|
2092
|
-
"vlan",
|
|
2093
|
-
"namespace",
|
|
2094
|
-
)
|
|
2095
|
-
.restrict(request.user, "view")
|
|
2096
|
-
.annotate(location_count=count_related(Location, "prefixes"))
|
|
2097
|
-
)
|
|
2446
|
+
prefixes = instance.prefixes.restrict(request.user, "view")
|
|
2098
2447
|
prefix_table = PrefixTable(prefixes)
|
|
2099
2448
|
prefix_table.columns.hide("role")
|
|
2100
2449
|
RequestConfig(request, paginate).configure(prefix_table)
|
|
2101
2450
|
context["prefix_table"] = prefix_table
|
|
2102
2451
|
if ContentType.objects.get_for_model(Rack) in context["content_types"]:
|
|
2103
|
-
racks = instance.racks.
|
|
2104
|
-
"location",
|
|
2105
|
-
"status",
|
|
2106
|
-
"tenant",
|
|
2107
|
-
"rack_group",
|
|
2108
|
-
).restrict(request.user, "view")
|
|
2452
|
+
racks = instance.racks.restrict(request.user, "view")
|
|
2109
2453
|
rack_table = RackTable(racks)
|
|
2110
2454
|
rack_table.columns.hide("role")
|
|
2111
2455
|
RequestConfig(request, paginate).configure(rack_table)
|
|
2112
2456
|
context["rack_table"] = rack_table
|
|
2113
2457
|
if ContentType.objects.get_for_model(VirtualMachine) in context["content_types"]:
|
|
2114
|
-
virtual_machines = instance.virtual_machines.
|
|
2115
|
-
"cluster",
|
|
2116
|
-
"role",
|
|
2117
|
-
"status",
|
|
2118
|
-
"tenant",
|
|
2119
|
-
).restrict(request.user, "view")
|
|
2458
|
+
virtual_machines = instance.virtual_machines.restrict(request.user, "view")
|
|
2120
2459
|
virtual_machine_table = VirtualMachineTable(virtual_machines)
|
|
2121
2460
|
virtual_machine_table.columns.hide("role")
|
|
2122
2461
|
RequestConfig(request, paginate).configure(virtual_machine_table)
|
|
2123
2462
|
context["virtual_machine_table"] = virtual_machine_table
|
|
2124
|
-
|
|
2463
|
+
if ContentType.objects.get_for_model(VMInterface) in context["content_types"]:
|
|
2464
|
+
vm_interfaces = instance.vm_interfaces.restrict(request.user, "view")
|
|
2465
|
+
vminterface_table = VMInterfaceTable(vm_interfaces)
|
|
2466
|
+
vminterface_table.columns.hide("role")
|
|
2467
|
+
RequestConfig(request, paginate).configure(vminterface_table)
|
|
2468
|
+
context["vminterface_table"] = vminterface_table
|
|
2125
2469
|
if ContentType.objects.get_for_model(VLAN) in context["content_types"]:
|
|
2126
|
-
vlans = (
|
|
2127
|
-
instance.vlans.annotate(location_count=count_related(Location, "vlans"))
|
|
2128
|
-
.select_related(
|
|
2129
|
-
"vlan_group",
|
|
2130
|
-
"status",
|
|
2131
|
-
"tenant",
|
|
2132
|
-
)
|
|
2133
|
-
.restrict(request.user, "view")
|
|
2134
|
-
)
|
|
2470
|
+
vlans = instance.vlans.restrict(request.user, "view")
|
|
2135
2471
|
vlan_table = VLANTable(vlans)
|
|
2136
2472
|
vlan_table.columns.hide("role")
|
|
2137
2473
|
RequestConfig(request, paginate).configure(vlan_table)
|
|
2138
2474
|
context["vlan_table"] = vlan_table
|
|
2475
|
+
if ContentType.objects.get_for_model(Module) in context["content_types"]:
|
|
2476
|
+
modules = instance.modules.restrict(request.user, "view")
|
|
2477
|
+
module_table = ModuleTable(modules)
|
|
2478
|
+
module_table.columns.hide("role")
|
|
2479
|
+
RequestConfig(request, paginate).configure(module_table)
|
|
2480
|
+
context["module_table"] = module_table
|
|
2139
2481
|
return context
|
|
2140
2482
|
|
|
2141
2483
|
|
|
@@ -2334,6 +2676,153 @@ class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
2334
2676
|
table = tables.SecretsGroupTable
|
|
2335
2677
|
|
|
2336
2678
|
|
|
2679
|
+
#
|
|
2680
|
+
# Static Groups
|
|
2681
|
+
#
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
class StaticGroupAssociationUIViewSet(
|
|
2685
|
+
ObjectBulkDestroyViewMixin,
|
|
2686
|
+
ObjectChangeLogViewMixin,
|
|
2687
|
+
ObjectDestroyViewMixin,
|
|
2688
|
+
ObjectDetailViewMixin,
|
|
2689
|
+
ObjectListViewMixin,
|
|
2690
|
+
# TODO anything else?
|
|
2691
|
+
):
|
|
2692
|
+
filterset_class = filters.StaticGroupAssociationFilterSet
|
|
2693
|
+
filterset_form_class = forms.StaticGroupAssociationFilterForm
|
|
2694
|
+
queryset = StaticGroupAssociation.all_objects.all()
|
|
2695
|
+
serializer_class = serializers.StaticGroupAssociationSerializer
|
|
2696
|
+
table_class = tables.StaticGroupAssociationTable
|
|
2697
|
+
action_buttons = ("export",)
|
|
2698
|
+
|
|
2699
|
+
def alter_queryset(self, request):
|
|
2700
|
+
queryset = super().alter_queryset(request)
|
|
2701
|
+
# Default to only showing associations for static-type groups:
|
|
2702
|
+
if request is None or "dynamic_group" not in request.GET:
|
|
2703
|
+
queryset = queryset.filter(dynamic_group__group_type=DynamicGroupTypeChoices.TYPE_STATIC)
|
|
2704
|
+
return queryset
|
|
2705
|
+
|
|
2706
|
+
|
|
2707
|
+
class DynamicGroupBulkAssignView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
2708
|
+
queryset = StaticGroupAssociation.objects.all()
|
|
2709
|
+
form_class = forms.DynamicGroupBulkAssignForm
|
|
2710
|
+
|
|
2711
|
+
def get_required_permission(self):
|
|
2712
|
+
return get_permission_for_model(self.queryset.model, "add")
|
|
2713
|
+
|
|
2714
|
+
def get(self, request):
|
|
2715
|
+
return redirect(self.get_return_url(request))
|
|
2716
|
+
|
|
2717
|
+
def post(self, request, **kwargs):
|
|
2718
|
+
"""
|
|
2719
|
+
Update the static group assignments of the provided `pk_list` (or `_all`) of the given `content_type`.
|
|
2720
|
+
|
|
2721
|
+
Unlike BulkEditView, this takes a single POST rather than two to perform its operation as
|
|
2722
|
+
there's no separate confirmation step involved.
|
|
2723
|
+
"""
|
|
2724
|
+
# TODO more error handling - content-type doesn't exist, model_class not found, filterset missing, etc.
|
|
2725
|
+
content_type = ContentType.objects.get(pk=request.POST.get("content_type"))
|
|
2726
|
+
model = content_type.model_class()
|
|
2727
|
+
self.default_return_url = get_route_for_model(model, "list")
|
|
2728
|
+
filterset_class = get_filterset_for_model(model)
|
|
2729
|
+
|
|
2730
|
+
if request.POST.get("_all"):
|
|
2731
|
+
if filterset_class is not None:
|
|
2732
|
+
pk_list = list(filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True))
|
|
2733
|
+
else:
|
|
2734
|
+
pk_list = list(model.objects.all().values_list("pk", flat=True))
|
|
2735
|
+
else:
|
|
2736
|
+
pk_list = request.POST.getlist("pk")
|
|
2737
|
+
|
|
2738
|
+
form = self.form_class(model, request.POST)
|
|
2739
|
+
restrict_form_fields(form, request.user)
|
|
2740
|
+
|
|
2741
|
+
if form.is_valid():
|
|
2742
|
+
logger.debug("Form validation was successful")
|
|
2743
|
+
try:
|
|
2744
|
+
with transaction.atomic():
|
|
2745
|
+
add_to_groups = list(form.cleaned_data["add_to_groups"])
|
|
2746
|
+
new_group_name = form.cleaned_data["create_and_assign_to_new_group_name"]
|
|
2747
|
+
if new_group_name:
|
|
2748
|
+
if not request.user.has_perm("extras.add_dynamicgroup"):
|
|
2749
|
+
raise DynamicGroup.DoesNotExist
|
|
2750
|
+
else:
|
|
2751
|
+
new_group = DynamicGroup(
|
|
2752
|
+
name=new_group_name,
|
|
2753
|
+
content_type=content_type,
|
|
2754
|
+
group_type=DynamicGroupTypeChoices.TYPE_STATIC,
|
|
2755
|
+
)
|
|
2756
|
+
new_group.validated_save()
|
|
2757
|
+
# Check permissions
|
|
2758
|
+
DynamicGroup.objects.restrict(request.user, "add").get(pk=new_group.pk)
|
|
2759
|
+
|
|
2760
|
+
add_to_groups.append(new_group)
|
|
2761
|
+
msg = "Created dynamic group"
|
|
2762
|
+
logger.info(f"{msg} {new_group} (PK: {new_group.pk})")
|
|
2763
|
+
msg = format_html('{} <a href="{}">{}</a>', msg, new_group.get_absolute_url(), new_group)
|
|
2764
|
+
messages.success(self.request, msg)
|
|
2765
|
+
|
|
2766
|
+
with deferred_change_logging_for_bulk_operation():
|
|
2767
|
+
associations = []
|
|
2768
|
+
for pk in pk_list:
|
|
2769
|
+
for dynamic_group in add_to_groups:
|
|
2770
|
+
association, created = StaticGroupAssociation.objects.get_or_create(
|
|
2771
|
+
dynamic_group=dynamic_group,
|
|
2772
|
+
associated_object_type_id=content_type.id,
|
|
2773
|
+
associated_object_id=pk,
|
|
2774
|
+
)
|
|
2775
|
+
association.validated_save()
|
|
2776
|
+
associations.append(association)
|
|
2777
|
+
if created:
|
|
2778
|
+
logger.debug("Created %s", association)
|
|
2779
|
+
|
|
2780
|
+
# Enforce object-level permissions
|
|
2781
|
+
if self.queryset.filter(pk__in=[assoc.pk for assoc in associations]).count() != len(
|
|
2782
|
+
associations
|
|
2783
|
+
):
|
|
2784
|
+
raise StaticGroupAssociation.DoesNotExist
|
|
2785
|
+
|
|
2786
|
+
if associations:
|
|
2787
|
+
msg = (
|
|
2788
|
+
f"Added {len(pk_list)} {model._meta.verbose_name_plural} "
|
|
2789
|
+
f"to {len(add_to_groups)} dynamic group(s)."
|
|
2790
|
+
)
|
|
2791
|
+
logger.info(msg)
|
|
2792
|
+
messages.success(self.request, msg)
|
|
2793
|
+
|
|
2794
|
+
if form.cleaned_data["remove_from_groups"]:
|
|
2795
|
+
for dynamic_group in form.cleaned_data["remove_from_groups"]:
|
|
2796
|
+
(
|
|
2797
|
+
StaticGroupAssociation.objects.restrict(request.user, "delete")
|
|
2798
|
+
.filter(
|
|
2799
|
+
dynamic_group=dynamic_group,
|
|
2800
|
+
associated_object_type=content_type,
|
|
2801
|
+
associated_object_id__in=pk_list,
|
|
2802
|
+
)
|
|
2803
|
+
.delete()
|
|
2804
|
+
)
|
|
2805
|
+
|
|
2806
|
+
msg = (
|
|
2807
|
+
f"Removed {len(pk_list)} {model._meta.verbose_name_plural} from "
|
|
2808
|
+
f"{len(form.cleaned_data['remove_from_groups'])} dynamic group(s)."
|
|
2809
|
+
)
|
|
2810
|
+
logger.info(msg)
|
|
2811
|
+
messages.success(self.request, msg)
|
|
2812
|
+
except ValidationError as e:
|
|
2813
|
+
messages.error(self.request, e)
|
|
2814
|
+
except ObjectDoesNotExist:
|
|
2815
|
+
msg = "Static group association failed due to object-level permissions violation"
|
|
2816
|
+
logger.warning(msg)
|
|
2817
|
+
messages.error(self.request, msg)
|
|
2818
|
+
|
|
2819
|
+
else:
|
|
2820
|
+
logger.debug("Form validation failed")
|
|
2821
|
+
messages.error(self.request, form.errors)
|
|
2822
|
+
|
|
2823
|
+
return redirect(self.get_return_url(request))
|
|
2824
|
+
|
|
2825
|
+
|
|
2337
2826
|
#
|
|
2338
2827
|
# Custom statuses
|
|
2339
2828
|
#
|