nautobot 2.2.8__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +113 -0
- nautobot/cloud/filters.py +187 -0
- nautobot/cloud/forms.py +339 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +246 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +157 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +125 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +110 -14
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +11 -3
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +28 -7
- nautobot/core/jobs/__init__.py +20 -3
- 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 +15 -19
- nautobot/core/settings.yaml +48 -13
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +130 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +28 -8
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +63 -14
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_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 +204 -2
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_templatetags_netutils.py +3 -3
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +119 -67
- nautobot/core/views/mixins.py +105 -36
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +81 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +252 -18
- nautobot/dcim/filters/__init__.py +373 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +834 -121
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +862 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +282 -99
- nautobot/dcim/models/devices.py +472 -13
- nautobot/dcim/models/racks.py +0 -1
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +248 -47
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +14 -6
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +693 -208
- nautobot/dcim/tests/test_filters.py +843 -217
- nautobot/dcim/tests/test_models.py +1103 -8
- nautobot/dcim/tests/test_views.py +1525 -343
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1109 -113
- 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 +73 -59
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +460 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +24 -5
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +26 -3
- 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 +87 -3
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +118 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +109 -101
- nautobot/extras/tables.py +201 -17
- 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 +11 -0
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +509 -23
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +6 -6
- nautobot/extras/tests/test_models.py +501 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +586 -8
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +85 -16
- nautobot/extras/views.py +562 -122
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +13 -0
- nautobot/ipam/forms.py +8 -4
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +20 -20
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +5 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +9 -2
- 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 +20 -34
- nautobot/project-static/css/base.css +21 -0
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +894 -90
- nautobot/project-static/docs/apps/index.html +894 -90
- nautobot/project-static/docs/apps/nautobot-apps.html +894 -90
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +921 -122
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +906 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1620 -905
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +937 -146
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +979 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +903 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +899 -95
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +993 -195
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +976 -133
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1080 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1244 -336
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1729 -877
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1166 -383
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2090 -1376
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2248 -1424
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +914 -113
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +965 -165
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1012 -225
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1915 -1279
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1848 -1104
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +906 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2335 -1701
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1804 -1026
- nautobot/project-static/docs/development/apps/api/configuration-view.html +894 -90
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/global-search.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/graphql.html +894 -90
- nautobot/project-static/docs/development/apps/api/models/index.html +944 -92
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +894 -90
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +894 -90
- nautobot/project-static/docs/development/apps/api/prometheus.html +894 -90
- nautobot/project-static/docs/development/apps/api/setup.html +894 -90
- nautobot/project-static/docs/development/apps/api/testing.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +894 -90
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/base-template.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/index.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/notes.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +894 -90
- nautobot/project-static/docs/development/apps/api/views/urls.html +894 -90
- nautobot/project-static/docs/development/apps/index.html +894 -90
- nautobot/project-static/docs/development/apps/migration/code-updates.html +894 -90
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +894 -90
- nautobot/project-static/docs/development/apps/migration/from-v1.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +894 -90
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +894 -90
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +894 -90
- nautobot/project-static/docs/development/core/application-registry.html +894 -90
- nautobot/project-static/docs/development/core/best-practices.html +895 -90
- nautobot/project-static/docs/development/core/bootstrap-ui.html +894 -90
- nautobot/project-static/docs/development/core/caching.html +894 -90
- nautobot/project-static/docs/development/core/controllers.html +894 -90
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +894 -90
- nautobot/project-static/docs/development/core/generic-views.html +894 -90
- nautobot/project-static/docs/development/core/getting-started.html +894 -90
- nautobot/project-static/docs/development/core/homepage.html +894 -90
- nautobot/project-static/docs/development/core/index.html +905 -90
- nautobot/project-static/docs/development/core/model-checklist.html +903 -91
- nautobot/project-static/docs/development/core/model-features.html +894 -90
- nautobot/project-static/docs/development/core/natural-keys.html +894 -90
- nautobot/project-static/docs/development/core/navigation-menu.html +894 -90
- nautobot/project-static/docs/development/core/release-checklist.html +897 -93
- nautobot/project-static/docs/development/core/role-internals.html +894 -90
- nautobot/project-static/docs/development/core/settings.html +894 -90
- nautobot/project-static/docs/development/core/style-guide.html +895 -91
- nautobot/project-static/docs/development/core/templates.html +906 -91
- nautobot/project-static/docs/development/core/testing.html +894 -90
- nautobot/project-static/docs/development/core/user-preferences.html +894 -90
- nautobot/project-static/docs/development/index.html +894 -90
- nautobot/project-static/docs/development/jobs/index.html +1271 -453
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +894 -90
- nautobot/project-static/docs/index.html +9032 -13
- 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 +902 -91
- nautobot/project-static/docs/overview/design_philosophy.html +896 -92
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +1131 -94
- nautobot/project-static/docs/release-notes/version-1.0.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.1.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.2.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.3.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.4.html +894 -90
- nautobot/project-static/docs/release-notes/version-1.5.html +895 -91
- nautobot/project-static/docs/release-notes/version-1.6.html +895 -91
- nautobot/project-static/docs/release-notes/version-2.0.html +894 -90
- nautobot/project-static/docs/release-notes/version-2.1.html +894 -90
- nautobot/project-static/docs/release-notes/version-2.2.html +1137 -196
- nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
- nautobot/project-static/docs/requirements.txt +5 -5
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +335 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +1025 -175
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +894 -90
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +902 -90
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +894 -90
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +946 -155
- nautobot/project-static/docs/user-guide/administration/installation/index.html +903 -95
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +936 -124
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +956 -159
- nautobot/project-static/docs/user-guide/administration/installation/services.html +915 -114
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +910 -101
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +894 -90
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +894 -90
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +894 -90
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +894 -90
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +977 -121
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +894 -90
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +895 -91
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +898 -90
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +897 -93
- 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 +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +925 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +915 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +922 -118
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +923 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +916 -107
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +938 -120
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +930 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +939 -121
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +930 -112
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +923 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +925 -117
- 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 +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +942 -85
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +945 -88
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +931 -127
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +938 -90
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +899 -91
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +899 -91
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +903 -98
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +894 -90
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +899 -91
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +894 -90
- 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 +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +894 -90
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +894 -90
- nautobot/project-static/docs/user-guide/index.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1260 -787
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +898 -90
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +897 -93
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +952 -123
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +894 -90
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +894 -90
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +24 -20
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/models.py +0 -2
- nautobot/virtualization/tables.py +12 -8
- 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.8.dist-info → nautobot-2.3.0.dist-info}/METADATA +21 -19
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/RECORD +684 -564
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
nautobot/extras/views.py
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
1
|
import logging
|
|
2
|
+
from urllib.parse import parse_qs
|
|
3
3
|
|
|
4
4
|
from celery import chain
|
|
5
5
|
from django.contrib import messages
|
|
6
|
+
from django.contrib.auth.models import AnonymousUser
|
|
6
7
|
from django.contrib.contenttypes.models import ContentType
|
|
7
8
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
8
|
-
from django.db import transaction
|
|
9
|
+
from django.db import IntegrityError, transaction
|
|
9
10
|
from django.db.models import ProtectedError, Q
|
|
10
11
|
from django.forms.utils import pretty_name
|
|
11
12
|
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|
12
13
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
13
14
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
14
15
|
from django.urls import reverse
|
|
16
|
+
from django.urls.exceptions import NoReverseMatch
|
|
15
17
|
from django.utils import timezone
|
|
16
18
|
from django.utils.encoding import iri_to_uri
|
|
17
19
|
from django.utils.html import format_html
|
|
@@ -19,38 +21,51 @@ from django.utils.http import url_has_allowed_host_and_scheme
|
|
|
19
21
|
from django.views.generic import View
|
|
20
22
|
from django_tables2 import RequestConfig
|
|
21
23
|
from jsonschema.validators import Draft7Validator
|
|
24
|
+
from rest_framework.decorators import action
|
|
22
25
|
|
|
23
26
|
from nautobot.core.forms import restrict_form_fields
|
|
24
27
|
from nautobot.core.models.querysets import count_related
|
|
25
28
|
from nautobot.core.models.utils import pretty_print_query
|
|
26
29
|
from nautobot.core.tables import ButtonsColumn
|
|
27
|
-
from nautobot.core.utils.
|
|
30
|
+
from nautobot.core.utils.config import get_settings_or_config
|
|
31
|
+
from nautobot.core.utils.lookup import (
|
|
32
|
+
get_filterset_for_model,
|
|
33
|
+
get_route_for_model,
|
|
34
|
+
get_table_class_string_from_view_name,
|
|
35
|
+
get_table_for_model,
|
|
36
|
+
)
|
|
37
|
+
from nautobot.core.utils.permissions import get_permission_for_model
|
|
28
38
|
from nautobot.core.utils.requests import normalize_querydict
|
|
29
39
|
from nautobot.core.views import generic, viewsets
|
|
30
40
|
from nautobot.core.views.mixins import (
|
|
41
|
+
GetReturnURLMixin,
|
|
31
42
|
ObjectBulkDestroyViewMixin,
|
|
32
43
|
ObjectBulkUpdateViewMixin,
|
|
44
|
+
ObjectChangeLogViewMixin,
|
|
33
45
|
ObjectDestroyViewMixin,
|
|
46
|
+
ObjectDetailViewMixin,
|
|
34
47
|
ObjectEditViewMixin,
|
|
48
|
+
ObjectListViewMixin,
|
|
35
49
|
ObjectPermissionRequiredMixin,
|
|
36
50
|
)
|
|
37
51
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
38
52
|
from nautobot.core.views.utils import prepare_cloned_fields
|
|
39
53
|
from nautobot.core.views.viewsets import NautobotUIViewSet
|
|
40
|
-
from nautobot.dcim.models import Controller, Device, Interface,
|
|
41
|
-
from nautobot.dcim.tables import ControllerTable, DeviceTable, RackTable
|
|
54
|
+
from nautobot.dcim.models import Controller, Device, Interface, Module, Rack
|
|
55
|
+
from nautobot.dcim.tables import ControllerTable, DeviceTable, InterfaceTable, ModuleTable, RackTable
|
|
42
56
|
from nautobot.extras.constants import JOB_OVERRIDABLE_FIELDS
|
|
57
|
+
from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
|
|
43
58
|
from nautobot.extras.signals import change_context_state
|
|
44
59
|
from nautobot.extras.tasks import delete_custom_field_data
|
|
45
60
|
from nautobot.extras.utils import get_base_template, get_worker_count
|
|
46
61
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
47
62
|
from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
|
|
48
63
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
49
|
-
from nautobot.virtualization.tables import VirtualMachineTable
|
|
64
|
+
from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
|
|
50
65
|
|
|
51
66
|
from . import filters, forms, tables
|
|
52
67
|
from .api import serializers
|
|
53
|
-
from .choices import JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
68
|
+
from .choices import DynamicGroupTypeChoices, JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
54
69
|
from .datasources import (
|
|
55
70
|
enqueue_git_repository_diff_origin_and_local,
|
|
56
71
|
enqueue_pull_git_repository_and_refresh_data,
|
|
@@ -76,19 +91,24 @@ from .models import (
|
|
|
76
91
|
JobHook,
|
|
77
92
|
JobLogEntry,
|
|
78
93
|
JobResult,
|
|
94
|
+
MetadataType,
|
|
79
95
|
Note,
|
|
80
96
|
ObjectChange,
|
|
97
|
+
ObjectMetadata,
|
|
81
98
|
Relationship,
|
|
82
99
|
RelationshipAssociation,
|
|
83
100
|
Role,
|
|
101
|
+
SavedView,
|
|
84
102
|
ScheduledJob,
|
|
85
103
|
Secret,
|
|
86
104
|
SecretsGroup,
|
|
87
105
|
SecretsGroupAssociation,
|
|
106
|
+
StaticGroupAssociation,
|
|
88
107
|
Status,
|
|
89
108
|
Tag,
|
|
90
109
|
TaggedItem,
|
|
91
110
|
Team,
|
|
111
|
+
UserSavedViewAssociation,
|
|
92
112
|
Webhook,
|
|
93
113
|
)
|
|
94
114
|
from .registry import registry
|
|
@@ -699,12 +719,19 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
699
719
|
|
|
700
720
|
def get_extra_context(self, request, instance):
|
|
701
721
|
context = super().get_extra_context(request, instance)
|
|
702
|
-
model = instance.
|
|
722
|
+
model = instance.model
|
|
703
723
|
table_class = get_table_for_model(model)
|
|
704
724
|
|
|
725
|
+
if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
|
|
726
|
+
# Ensure that members cache is up-to-date for this specific group
|
|
727
|
+
members = instance.update_cached_members()
|
|
728
|
+
messages.success(request, f"Refreshed cached members list for {instance}")
|
|
729
|
+
else:
|
|
730
|
+
members = instance.members
|
|
731
|
+
|
|
705
732
|
if table_class is not None:
|
|
706
733
|
# Members table (for display on Members nav tab)
|
|
707
|
-
members_table = table_class(
|
|
734
|
+
members_table = table_class(members.restrict(request.user, "view"), orderable=False)
|
|
708
735
|
paginate = {
|
|
709
736
|
"paginator_class": EnhancedPaginator,
|
|
710
737
|
"per_page": get_paginate_count(request),
|
|
@@ -724,7 +751,16 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
724
751
|
ancestors_table = tables.NestedDynamicGroupAncestorsTable(ancestors, orderable=False)
|
|
725
752
|
ancestors_tree = instance.flatten_ancestors_tree(instance.ancestors_tree())
|
|
726
753
|
|
|
727
|
-
|
|
754
|
+
if instance.group_type != DynamicGroupTypeChoices.TYPE_STATIC:
|
|
755
|
+
context["raw_query"] = pretty_print_query(instance.generate_query())
|
|
756
|
+
context["members_list_url"] = None
|
|
757
|
+
else:
|
|
758
|
+
context["raw_query"] = None
|
|
759
|
+
try:
|
|
760
|
+
context["members_list_url"] = reverse(get_route_for_model(instance.model, "list"))
|
|
761
|
+
except NoReverseMatch:
|
|
762
|
+
context["members_list_url"] = None
|
|
763
|
+
context["members_verbose_name_plural"] = instance.model._meta.verbose_name_plural
|
|
728
764
|
context["members_table"] = members_table
|
|
729
765
|
context["ancestors_table"] = ancestors_table
|
|
730
766
|
context["ancestors_tree"] = ancestors_tree
|
|
@@ -776,16 +812,19 @@ class DynamicGroupEditView(generic.ObjectEditView):
|
|
|
776
812
|
# Obtain the instance, but do not yet `save()` it to the database.
|
|
777
813
|
obj = form.save(commit=False)
|
|
778
814
|
|
|
779
|
-
# Process the filter form and save the query filters to `obj.filter`.
|
|
780
815
|
ctx = self.get_extra_context(request, obj)
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
816
|
+
if obj.group_type == DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER:
|
|
817
|
+
# Process the filter form and save the query filters to `obj.filter`.
|
|
818
|
+
filter_form = ctx["filter_form"]
|
|
819
|
+
if filter_form.is_valid():
|
|
820
|
+
obj.set_filter(filter_form.cleaned_data)
|
|
821
|
+
else:
|
|
822
|
+
raise RuntimeError(filter_form.errors)
|
|
786
823
|
|
|
787
824
|
# After filters have been set, now we save the object to the database.
|
|
788
825
|
obj.save()
|
|
826
|
+
# Save m2m fields, such as Tags https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/#the-save-method
|
|
827
|
+
form.save_m2m()
|
|
789
828
|
# Check that the new object conforms with any assigned object-level permissions
|
|
790
829
|
self.queryset.get(pk=obj.pk)
|
|
791
830
|
|
|
@@ -868,6 +907,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
868
907
|
filterset = filters.DynamicGroupFilterSet
|
|
869
908
|
|
|
870
909
|
|
|
910
|
+
# 3.0 TODO: remove, deprecated since 2.3 (#5845)
|
|
871
911
|
class ObjectDynamicGroupsView(generic.GenericView):
|
|
872
912
|
"""
|
|
873
913
|
Present a list of dynamic groups associated to a particular object.
|
|
@@ -884,16 +924,18 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
884
924
|
obj = get_object_or_404(model, **kwargs)
|
|
885
925
|
|
|
886
926
|
# Gather all dynamic groups for this object (and its related objects)
|
|
887
|
-
|
|
888
|
-
data=obj.
|
|
927
|
+
dynamicgroups_table = tables.DynamicGroupTable(
|
|
928
|
+
data=obj.dynamic_groups.restrict(request.user, "view"), orderable=False
|
|
889
929
|
)
|
|
930
|
+
dynamicgroups_table.columns.hide("content_type")
|
|
931
|
+
dynamicgroups_table.columns.hide("members")
|
|
890
932
|
|
|
891
933
|
# Apply the request context
|
|
892
934
|
paginate = {
|
|
893
935
|
"paginator_class": EnhancedPaginator,
|
|
894
936
|
"per_page": get_paginate_count(request),
|
|
895
937
|
}
|
|
896
|
-
RequestConfig(request, paginate).configure(
|
|
938
|
+
RequestConfig(request, paginate).configure(dynamicgroups_table)
|
|
897
939
|
|
|
898
940
|
self.base_template = get_base_template(self.base_template, model)
|
|
899
941
|
|
|
@@ -904,7 +946,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
904
946
|
"object": obj,
|
|
905
947
|
"verbose_name": obj._meta.verbose_name,
|
|
906
948
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
907
|
-
"table":
|
|
949
|
+
"table": dynamicgroups_table,
|
|
908
950
|
"base_template": self.base_template,
|
|
909
951
|
"active_tab": "dynamic-groups",
|
|
910
952
|
},
|
|
@@ -1345,55 +1387,25 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
1345
1387
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
1346
1388
|
|
|
1347
1389
|
if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
# as a once in the future task with the due date set to the current time. This means
|
|
1354
|
-
# when approval is granted, the task is immediately due for execution.
|
|
1355
|
-
schedule_type = JobExecutionType.TYPE_FUTURE
|
|
1356
|
-
schedule_datetime = timezone.now()
|
|
1357
|
-
schedule_name = f"{job_model} - {schedule_datetime}"
|
|
1358
|
-
|
|
1359
|
-
else:
|
|
1360
|
-
schedule_name = schedule_form.cleaned_data["_schedule_name"]
|
|
1361
|
-
|
|
1362
|
-
if schedule_type == JobExecutionType.TYPE_CUSTOM:
|
|
1363
|
-
crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
|
|
1364
|
-
# doing .get("key", "default") returns None instead of "default" here for some reason
|
|
1365
|
-
schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
|
|
1366
|
-
if schedule_datetime is None:
|
|
1367
|
-
# "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
|
|
1368
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
1369
|
-
schedule_datetime = timezone.now() + timedelta(seconds=20)
|
|
1370
|
-
else:
|
|
1371
|
-
schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
|
|
1372
|
-
|
|
1373
|
-
celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
|
|
1374
|
-
scheduled_job = ScheduledJob(
|
|
1375
|
-
name=schedule_name,
|
|
1376
|
-
task=job_model.class_path,
|
|
1377
|
-
job_model=job_model,
|
|
1378
|
-
start_time=schedule_datetime,
|
|
1379
|
-
description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
|
|
1380
|
-
kwargs=job_class.serialize_data(job_form.cleaned_data),
|
|
1381
|
-
celery_kwargs=celery_kwargs,
|
|
1390
|
+
scheduled_job = ScheduledJob.create_schedule(
|
|
1391
|
+
job_model,
|
|
1392
|
+
request.user,
|
|
1393
|
+
name=schedule_form.cleaned_data.get("_schedule_name"),
|
|
1394
|
+
start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
|
|
1382
1395
|
interval=schedule_type,
|
|
1383
|
-
|
|
1384
|
-
queue=task_queue,
|
|
1385
|
-
user=request.user,
|
|
1396
|
+
crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
|
|
1386
1397
|
approval_required=job_model.approval_required,
|
|
1387
|
-
|
|
1398
|
+
task_queue=task_queue,
|
|
1399
|
+
profile=profile,
|
|
1400
|
+
**job_class.serialize_data(job_form.cleaned_data),
|
|
1388
1401
|
)
|
|
1389
|
-
scheduled_job.validated_save()
|
|
1390
1402
|
|
|
1391
1403
|
if job_model.approval_required:
|
|
1392
|
-
messages.success(request, f"Job {
|
|
1393
|
-
return redirect(return_url
|
|
1404
|
+
messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
|
|
1405
|
+
return redirect(return_url or "extras:scheduledjob_approval_queue_list")
|
|
1394
1406
|
else:
|
|
1395
|
-
messages.success(request, f"Job {
|
|
1396
|
-
return redirect(return_url
|
|
1407
|
+
messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
|
|
1408
|
+
return redirect(return_url or "extras:scheduledjob_list")
|
|
1397
1409
|
|
|
1398
1410
|
else:
|
|
1399
1411
|
# Enqueue job for immediate execution
|
|
@@ -1614,6 +1626,257 @@ class JobApprovalRequestView(generic.ObjectView):
|
|
|
1614
1626
|
)
|
|
1615
1627
|
|
|
1616
1628
|
|
|
1629
|
+
#
|
|
1630
|
+
# Saved Views
|
|
1631
|
+
#
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
class SavedViewUIViewSet(
|
|
1635
|
+
ObjectDetailViewMixin,
|
|
1636
|
+
ObjectChangeLogViewMixin,
|
|
1637
|
+
ObjectDestroyViewMixin,
|
|
1638
|
+
ObjectEditViewMixin,
|
|
1639
|
+
ObjectListViewMixin,
|
|
1640
|
+
):
|
|
1641
|
+
queryset = SavedView.objects.all()
|
|
1642
|
+
form_class = forms.SavedViewForm
|
|
1643
|
+
filterset_class = filters.SavedViewFilterSet
|
|
1644
|
+
serializer_class = serializers.SavedViewSerializer
|
|
1645
|
+
table_class = tables.SavedViewTable
|
|
1646
|
+
action_buttons = ("export",)
|
|
1647
|
+
|
|
1648
|
+
def alter_queryset(self, request):
|
|
1649
|
+
"""
|
|
1650
|
+
Two scenarios we need to handle here:
|
|
1651
|
+
1. User can view all saved views with extras.view_savedview permission.
|
|
1652
|
+
2. User without the permission can only view shared savedviews and his/her own saved views.
|
|
1653
|
+
"""
|
|
1654
|
+
queryset = super().alter_queryset(request)
|
|
1655
|
+
user = request.user
|
|
1656
|
+
if user.has_perms(["extras.view_savedview"]):
|
|
1657
|
+
saved_views = queryset.restrict(user, "view")
|
|
1658
|
+
else:
|
|
1659
|
+
shared_saved_views = queryset.filter(is_shared=True)
|
|
1660
|
+
user_owned_saved_views = queryset.filter(owner=user)
|
|
1661
|
+
saved_views = shared_saved_views | user_owned_saved_views
|
|
1662
|
+
return saved_views
|
|
1663
|
+
|
|
1664
|
+
def get_queryset(self):
|
|
1665
|
+
"""
|
|
1666
|
+
Get the list of items for this view.
|
|
1667
|
+
All users should be able to see saved views so we do not apply extra permissions.
|
|
1668
|
+
"""
|
|
1669
|
+
return self.queryset.all()
|
|
1670
|
+
|
|
1671
|
+
def check_permissions(self, request):
|
|
1672
|
+
"""
|
|
1673
|
+
Override this method to not check any permissions.
|
|
1674
|
+
Since users with <app_label>.view_<model_name> permissions should be able to view saved views related to this model.
|
|
1675
|
+
And those permissions will be enforced in the related view.
|
|
1676
|
+
"""
|
|
1677
|
+
|
|
1678
|
+
def dispatch(self, request, *args, **kwargs):
|
|
1679
|
+
if isinstance(request.user, AnonymousUser):
|
|
1680
|
+
return self.handle_no_permission()
|
|
1681
|
+
return super().dispatch(request, *args, **kwargs)
|
|
1682
|
+
|
|
1683
|
+
def extra_message_context(self, obj):
|
|
1684
|
+
"""
|
|
1685
|
+
Context variables for this extra message.
|
|
1686
|
+
"""
|
|
1687
|
+
return {"new_global_default_view": obj}
|
|
1688
|
+
|
|
1689
|
+
def extra_message(self, **kwargs):
|
|
1690
|
+
new_global_default_view = kwargs.get("new_global_default_view")
|
|
1691
|
+
view_name = new_global_default_view.view
|
|
1692
|
+
message = ""
|
|
1693
|
+
if new_global_default_view.is_global_default:
|
|
1694
|
+
message = format_html(
|
|
1695
|
+
'<br>The global default saved view for "{}" is set to <a href="{}">{}</a>',
|
|
1696
|
+
view_name,
|
|
1697
|
+
new_global_default_view.get_absolute_url(),
|
|
1698
|
+
new_global_default_view.name,
|
|
1699
|
+
)
|
|
1700
|
+
return message
|
|
1701
|
+
|
|
1702
|
+
def list(self, request, *args, **kwargs):
|
|
1703
|
+
if not request.user.has_perms(["extras.view_savedview"]):
|
|
1704
|
+
return self.handle_no_permission()
|
|
1705
|
+
return super().list(request, *args, **kwargs)
|
|
1706
|
+
|
|
1707
|
+
def retrieve(self, request, *args, **kwargs):
|
|
1708
|
+
"""
|
|
1709
|
+
The detail view for a saved view should the related ObjectListView with saved configurations applied
|
|
1710
|
+
"""
|
|
1711
|
+
instance = self.get_object()
|
|
1712
|
+
list_view_url = reverse(instance.view) + f"?saved_view={instance.pk}"
|
|
1713
|
+
return redirect(list_view_url)
|
|
1714
|
+
|
|
1715
|
+
@action(detail=True, name="Set Default", methods=["get"], url_path="set-default", url_name="set_default")
|
|
1716
|
+
def set_default(self, request, *args, **kwargs):
|
|
1717
|
+
"""
|
|
1718
|
+
Set current saved view as the the request.user default view. Overriding the global default view if there is one.
|
|
1719
|
+
"""
|
|
1720
|
+
user = request.user
|
|
1721
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1722
|
+
UserSavedViewAssociation.objects.filter(user=user, view_name=sv.view).delete()
|
|
1723
|
+
UserSavedViewAssociation.objects.create(user=user, saved_view=sv, view_name=sv.view)
|
|
1724
|
+
list_view_url = sv.get_absolute_url()
|
|
1725
|
+
messages.success(
|
|
1726
|
+
request, f"Successfully set current view '{sv.name}' as the default '{sv.view}' view for user {user}"
|
|
1727
|
+
)
|
|
1728
|
+
return redirect(list_view_url)
|
|
1729
|
+
|
|
1730
|
+
@action(detail=True, name="Update Config", methods=["get"], url_path="update-config", url_name="update_config")
|
|
1731
|
+
def update_saved_view_config(self, request, *args, **kwargs):
|
|
1732
|
+
"""
|
|
1733
|
+
Extract filter_params, pagination and sort_order from request.GET and apply it to the SavedView specified
|
|
1734
|
+
"""
|
|
1735
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1736
|
+
if sv.owner == request.user or request.user.has_perms(["extras.change_savedview"]):
|
|
1737
|
+
pass
|
|
1738
|
+
else:
|
|
1739
|
+
messages.error(
|
|
1740
|
+
request, f"You do not have the required permission to modify this Saved View owned by {sv.owner}"
|
|
1741
|
+
)
|
|
1742
|
+
return redirect(self.get_return_url(request, obj=sv))
|
|
1743
|
+
table_changes_pending = request.GET.get("table_changes_pending", False)
|
|
1744
|
+
all_filters_removed = request.GET.get("all_filters_removed", False)
|
|
1745
|
+
pagination_count = request.GET.get("per_page", None)
|
|
1746
|
+
if pagination_count is not None:
|
|
1747
|
+
sv.config["pagination_count"] = int(pagination_count)
|
|
1748
|
+
sort_order = request.GET.getlist("sort", [])
|
|
1749
|
+
if sort_order:
|
|
1750
|
+
sv.config["sort_order"] = sort_order
|
|
1751
|
+
|
|
1752
|
+
filter_params = {}
|
|
1753
|
+
for key in request.GET:
|
|
1754
|
+
if key in self.non_filter_params:
|
|
1755
|
+
continue
|
|
1756
|
+
# TODO: this is fragile, other single-value filters will also be unhappy if given a list
|
|
1757
|
+
if key == "q":
|
|
1758
|
+
filter_params[key] = request.GET.get(key)
|
|
1759
|
+
else:
|
|
1760
|
+
filter_params[key] = request.GET.getlist(key)
|
|
1761
|
+
|
|
1762
|
+
if filter_params:
|
|
1763
|
+
sv.config["filter_params"] = filter_params
|
|
1764
|
+
elif all_filters_removed:
|
|
1765
|
+
sv.config["filter_params"] = {}
|
|
1766
|
+
|
|
1767
|
+
if table_changes_pending:
|
|
1768
|
+
table_class = get_table_class_string_from_view_name(sv.view)
|
|
1769
|
+
if table_class:
|
|
1770
|
+
if sv.config.get("table_config", None) is None:
|
|
1771
|
+
sv.config["table_config"] = {}
|
|
1772
|
+
sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
|
|
1773
|
+
|
|
1774
|
+
sv.validated_save()
|
|
1775
|
+
list_view_url = sv.get_absolute_url()
|
|
1776
|
+
messages.success(request, f"Successfully updated current view {sv.name}")
|
|
1777
|
+
return redirect(list_view_url)
|
|
1778
|
+
|
|
1779
|
+
def create(self, request, *args, **kwargs):
|
|
1780
|
+
"""
|
|
1781
|
+
This method will extract filter_params, pagination and sort_order from request.GET
|
|
1782
|
+
and the name of the new SavedView from request.POST to create a new SavedView.
|
|
1783
|
+
"""
|
|
1784
|
+
name = request.POST.get("name")
|
|
1785
|
+
is_shared = request.POST.get("is_shared", False)
|
|
1786
|
+
if is_shared:
|
|
1787
|
+
is_shared = True
|
|
1788
|
+
params = request.POST.get("params", "")
|
|
1789
|
+
|
|
1790
|
+
param_dict = parse_qs(params)
|
|
1791
|
+
|
|
1792
|
+
single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "q", "per_page"]
|
|
1793
|
+
for key in param_dict.keys():
|
|
1794
|
+
if key in single_value_params:
|
|
1795
|
+
param_dict[key] = param_dict[key][0]
|
|
1796
|
+
|
|
1797
|
+
derived_view_pk = param_dict.get("saved_view", None)
|
|
1798
|
+
derived_instance = None
|
|
1799
|
+
if derived_view_pk:
|
|
1800
|
+
derived_instance = self.get_queryset().get(pk=derived_view_pk)
|
|
1801
|
+
view_name = request.POST.get("view")
|
|
1802
|
+
try:
|
|
1803
|
+
reverse(view_name)
|
|
1804
|
+
except NoReverseMatch:
|
|
1805
|
+
messages.error(request, f"Invalid view name {view_name} specified.")
|
|
1806
|
+
if derived_view_pk:
|
|
1807
|
+
return redirect(self.get_return_url(request, obj=derived_instance))
|
|
1808
|
+
else:
|
|
1809
|
+
return redirect(self.get_return_url(request))
|
|
1810
|
+
table_changes_pending = param_dict.get("table_changes_pending", False)
|
|
1811
|
+
all_filters_removed = param_dict.get("all_filters_removed", False)
|
|
1812
|
+
try:
|
|
1813
|
+
sv = SavedView.objects.create(name=name, owner=request.user, view=view_name, is_shared=is_shared)
|
|
1814
|
+
except IntegrityError:
|
|
1815
|
+
messages.error(request, f"You already have a Saved View named '{name}' for this view '{view_name}'")
|
|
1816
|
+
if derived_view_pk:
|
|
1817
|
+
return redirect(self.get_return_url(request, obj=derived_instance))
|
|
1818
|
+
else:
|
|
1819
|
+
return redirect(reverse(view_name))
|
|
1820
|
+
pagination_count = param_dict.get("per_page", None)
|
|
1821
|
+
if not pagination_count:
|
|
1822
|
+
if derived_instance and derived_instance.config.get("pagination_count", None):
|
|
1823
|
+
pagination_count = derived_instance.config["pagination_count"]
|
|
1824
|
+
else:
|
|
1825
|
+
pagination_count = get_settings_or_config("PAGINATE_COUNT")
|
|
1826
|
+
sv.config["pagination_count"] = int(pagination_count)
|
|
1827
|
+
sort_order = param_dict.get("sort", [])
|
|
1828
|
+
if not sort_order:
|
|
1829
|
+
if derived_instance:
|
|
1830
|
+
sort_order = derived_instance.config.get("sort_order", [])
|
|
1831
|
+
sv.config["sort_order"] = sort_order
|
|
1832
|
+
|
|
1833
|
+
sv.config["filter_params"] = {}
|
|
1834
|
+
for key in param_dict:
|
|
1835
|
+
if key in [*self.non_filter_params, "view"]:
|
|
1836
|
+
continue
|
|
1837
|
+
sv.config["filter_params"][key] = param_dict.get(key)
|
|
1838
|
+
if not sv.config["filter_params"]:
|
|
1839
|
+
if derived_instance and all_filters_removed:
|
|
1840
|
+
sv.config["filter_params"] = {}
|
|
1841
|
+
elif derived_instance:
|
|
1842
|
+
sv.config["filter_params"] = derived_instance.config["filter_params"]
|
|
1843
|
+
|
|
1844
|
+
table_class = get_table_class_string_from_view_name(view_name)
|
|
1845
|
+
sv.config["table_config"] = {}
|
|
1846
|
+
if table_class:
|
|
1847
|
+
if table_changes_pending or derived_instance is None:
|
|
1848
|
+
sv.config["table_config"][f"{table_class}"] = request.user.get_config(f"tables.{table_class}")
|
|
1849
|
+
elif derived_instance.config.get("table_config") and derived_instance.config["table_config"].get(
|
|
1850
|
+
f"{table_class}"
|
|
1851
|
+
):
|
|
1852
|
+
sv.config["table_config"][f"{table_class}"] = derived_instance.config["table_config"][f"{table_class}"]
|
|
1853
|
+
try:
|
|
1854
|
+
sv.validated_save()
|
|
1855
|
+
list_view_url = sv.get_absolute_url()
|
|
1856
|
+
message = f"Successfully created new Saved View '{sv.name}'."
|
|
1857
|
+
messages.success(request, message)
|
|
1858
|
+
return redirect(list_view_url)
|
|
1859
|
+
except ValidationError as e:
|
|
1860
|
+
messages.error(request, e)
|
|
1861
|
+
return redirect(self.get_return_url(request))
|
|
1862
|
+
|
|
1863
|
+
def destroy(self, request, *args, **kwargs):
|
|
1864
|
+
"""
|
|
1865
|
+
request.GET: render the ObjectDeleteConfirmationForm which is passed to NautobotHTMLRenderer as Response.
|
|
1866
|
+
request.POST: call perform_destroy() which validates the form and perform the action of delete.
|
|
1867
|
+
Override to add more variables to Response
|
|
1868
|
+
"""
|
|
1869
|
+
sv = SavedView.objects.get(pk=kwargs.get("pk", None))
|
|
1870
|
+
if sv.owner == request.user or request.user.has_perms(["extras.delete_savedview"]):
|
|
1871
|
+
pass
|
|
1872
|
+
else:
|
|
1873
|
+
messages.error(
|
|
1874
|
+
request, f"You do not have the required permission to delete this Saved View owned by {sv.owner}"
|
|
1875
|
+
)
|
|
1876
|
+
return redirect(self.get_return_url(request, obj=sv))
|
|
1877
|
+
return super().destroy(request, *args, **kwargs)
|
|
1878
|
+
|
|
1879
|
+
|
|
1617
1880
|
class ScheduledJobListView(generic.ObjectListView):
|
|
1618
1881
|
queryset = ScheduledJob.objects.enabled()
|
|
1619
1882
|
table = tables.ScheduledJobTable
|
|
@@ -1787,8 +2050,13 @@ class JobLogEntryTableView(generic.GenericView):
|
|
|
1787
2050
|
else:
|
|
1788
2051
|
queryset = instance.job_log_entries.all()
|
|
1789
2052
|
log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
|
|
1790
|
-
|
|
1791
|
-
|
|
2053
|
+
paginate = {
|
|
2054
|
+
"paginator_class": EnhancedPaginator,
|
|
2055
|
+
"per_page": get_paginate_count(request),
|
|
2056
|
+
}
|
|
2057
|
+
RequestConfig(request, paginate).configure(log_table)
|
|
2058
|
+
table = log_table.as_html(request)
|
|
2059
|
+
return HttpResponse(table)
|
|
1792
2060
|
|
|
1793
2061
|
|
|
1794
2062
|
#
|
|
@@ -1908,6 +2176,58 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
1908
2176
|
)
|
|
1909
2177
|
|
|
1910
2178
|
|
|
2179
|
+
#
|
|
2180
|
+
# Metadata
|
|
2181
|
+
#
|
|
2182
|
+
|
|
2183
|
+
|
|
2184
|
+
class MetadataTypeUIViewSet(NautobotUIViewSet):
|
|
2185
|
+
bulk_update_form_class = forms.MetadataTypeBulkEditForm
|
|
2186
|
+
filterset_class = filters.MetadataTypeFilterSet
|
|
2187
|
+
filterset_form_class = forms.MetadataTypeFilterForm
|
|
2188
|
+
form_class = forms.MetadataTypeForm
|
|
2189
|
+
queryset = MetadataType.objects.all()
|
|
2190
|
+
serializer_class = serializers.MetadataTypeSerializer
|
|
2191
|
+
table_class = tables.MetadataTypeTable
|
|
2192
|
+
|
|
2193
|
+
def get_extra_context(self, request, instance):
|
|
2194
|
+
context = super().get_extra_context(request, instance)
|
|
2195
|
+
|
|
2196
|
+
if self.action in ("create", "update"):
|
|
2197
|
+
if request.POST:
|
|
2198
|
+
context["choices"] = forms.MetadataChoiceFormSet(data=request.POST, instance=instance)
|
|
2199
|
+
else:
|
|
2200
|
+
context["choices"] = forms.MetadataChoiceFormSet(instance=instance)
|
|
2201
|
+
|
|
2202
|
+
return context
|
|
2203
|
+
|
|
2204
|
+
def form_save(self, form, **kwargs):
|
|
2205
|
+
obj = super().form_save(form, **kwargs)
|
|
2206
|
+
|
|
2207
|
+
# Process the formset for choices
|
|
2208
|
+
ctx = self.get_extra_context(self.request, obj)
|
|
2209
|
+
choices = ctx["choices"]
|
|
2210
|
+
if choices.is_valid():
|
|
2211
|
+
choices.save()
|
|
2212
|
+
else:
|
|
2213
|
+
raise ValidationError(choices.errors)
|
|
2214
|
+
|
|
2215
|
+
return obj
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
class ObjectMetadataUIViewSet(
|
|
2219
|
+
ObjectChangeLogViewMixin,
|
|
2220
|
+
ObjectDetailViewMixin,
|
|
2221
|
+
ObjectListViewMixin,
|
|
2222
|
+
):
|
|
2223
|
+
filterset_class = filters.ObjectMetadataFilterSet
|
|
2224
|
+
filterset_form_class = forms.ObjectMetadataFilterForm
|
|
2225
|
+
queryset = ObjectMetadata.objects.all().order_by("assigned_object_type", "assigned_object_id", "scoped_fields")
|
|
2226
|
+
serializer_class = serializers.ObjectMetadataSerializer
|
|
2227
|
+
table_class = tables.ObjectMetadataTable
|
|
2228
|
+
action_buttons = ("export",)
|
|
2229
|
+
|
|
2230
|
+
|
|
1911
2231
|
#
|
|
1912
2232
|
# Notes
|
|
1913
2233
|
#
|
|
@@ -2067,43 +2387,32 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2067
2387
|
}
|
|
2068
2388
|
|
|
2069
2389
|
if ContentType.objects.get_for_model(Device) in context["content_types"]:
|
|
2070
|
-
devices = instance.devices.
|
|
2071
|
-
"status",
|
|
2072
|
-
"location",
|
|
2073
|
-
"tenant",
|
|
2074
|
-
"role",
|
|
2075
|
-
"rack",
|
|
2076
|
-
"device_type",
|
|
2077
|
-
).restrict(request.user, "view")
|
|
2390
|
+
devices = instance.devices.restrict(request.user, "view")
|
|
2078
2391
|
device_table = DeviceTable(devices)
|
|
2079
2392
|
device_table.columns.hide("role")
|
|
2080
2393
|
RequestConfig(request, paginate).configure(device_table)
|
|
2081
2394
|
context["device_table"] = device_table
|
|
2082
2395
|
|
|
2396
|
+
if ContentType.objects.get_for_model(Interface) in context["content_types"]:
|
|
2397
|
+
interfaces = instance.interfaces.restrict(request.user, "view")
|
|
2398
|
+
interface_table = InterfaceTable(interfaces)
|
|
2399
|
+
interface_table.columns.hide("role")
|
|
2400
|
+
RequestConfig(request, paginate).configure(interface_table)
|
|
2401
|
+
context["interface_table"] = interface_table
|
|
2402
|
+
|
|
2083
2403
|
if ContentType.objects.get_for_model(Controller) in context["content_types"]:
|
|
2084
|
-
controllers = instance.controllers.
|
|
2085
|
-
"status",
|
|
2086
|
-
"location",
|
|
2087
|
-
"tenant",
|
|
2088
|
-
"role",
|
|
2089
|
-
).restrict(request.user, "view")
|
|
2404
|
+
controllers = instance.controllers.restrict(request.user, "view")
|
|
2090
2405
|
controller_table = ControllerTable(controllers)
|
|
2091
2406
|
controller_table.columns.hide("role")
|
|
2092
2407
|
RequestConfig(request, paginate).configure(controller_table)
|
|
2093
2408
|
context["controller_table"] = controller_table
|
|
2094
2409
|
|
|
2095
2410
|
if ContentType.objects.get_for_model(IPAddress) in context["content_types"]:
|
|
2096
|
-
ipaddress = (
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
|
|
2102
|
-
vm_interface_count=count_related(VMInterface, "ip_addresses"),
|
|
2103
|
-
vm_interface_parent_count=count_related(
|
|
2104
|
-
VirtualMachine, "interfaces__ip_addresses", distinct=True
|
|
2105
|
-
),
|
|
2106
|
-
)
|
|
2411
|
+
ipaddress = instance.ip_addresses.restrict(request.user, "view").annotate(
|
|
2412
|
+
interface_count=count_related(Interface, "ip_addresses"),
|
|
2413
|
+
interface_parent_count=count_related(Device, "interfaces__ip_addresses", distinct=True),
|
|
2414
|
+
vm_interface_count=count_related(VMInterface, "ip_addresses"),
|
|
2415
|
+
vm_interface_parent_count=count_related(VirtualMachine, "interfaces__ip_addresses", distinct=True),
|
|
2107
2416
|
)
|
|
2108
2417
|
ipaddress_table = IPAddressTable(ipaddress)
|
|
2109
2418
|
ipaddress_table.columns.hide("role")
|
|
@@ -2111,57 +2420,41 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2111
2420
|
context["ipaddress_table"] = ipaddress_table
|
|
2112
2421
|
|
|
2113
2422
|
if ContentType.objects.get_for_model(Prefix) in context["content_types"]:
|
|
2114
|
-
prefixes = (
|
|
2115
|
-
instance.prefixes.select_related(
|
|
2116
|
-
"status",
|
|
2117
|
-
"tenant",
|
|
2118
|
-
"vlan",
|
|
2119
|
-
"namespace",
|
|
2120
|
-
)
|
|
2121
|
-
.restrict(request.user, "view")
|
|
2122
|
-
.annotate(location_count=count_related(Location, "prefixes"))
|
|
2123
|
-
)
|
|
2423
|
+
prefixes = instance.prefixes.restrict(request.user, "view")
|
|
2124
2424
|
prefix_table = PrefixTable(prefixes)
|
|
2125
2425
|
prefix_table.columns.hide("role")
|
|
2126
2426
|
RequestConfig(request, paginate).configure(prefix_table)
|
|
2127
2427
|
context["prefix_table"] = prefix_table
|
|
2128
2428
|
if ContentType.objects.get_for_model(Rack) in context["content_types"]:
|
|
2129
|
-
racks = instance.racks.
|
|
2130
|
-
"location",
|
|
2131
|
-
"status",
|
|
2132
|
-
"tenant",
|
|
2133
|
-
"rack_group",
|
|
2134
|
-
).restrict(request.user, "view")
|
|
2429
|
+
racks = instance.racks.restrict(request.user, "view")
|
|
2135
2430
|
rack_table = RackTable(racks)
|
|
2136
2431
|
rack_table.columns.hide("role")
|
|
2137
2432
|
RequestConfig(request, paginate).configure(rack_table)
|
|
2138
2433
|
context["rack_table"] = rack_table
|
|
2139
2434
|
if ContentType.objects.get_for_model(VirtualMachine) in context["content_types"]:
|
|
2140
|
-
virtual_machines = instance.virtual_machines.
|
|
2141
|
-
"cluster",
|
|
2142
|
-
"role",
|
|
2143
|
-
"status",
|
|
2144
|
-
"tenant",
|
|
2145
|
-
).restrict(request.user, "view")
|
|
2435
|
+
virtual_machines = instance.virtual_machines.restrict(request.user, "view")
|
|
2146
2436
|
virtual_machine_table = VirtualMachineTable(virtual_machines)
|
|
2147
2437
|
virtual_machine_table.columns.hide("role")
|
|
2148
2438
|
RequestConfig(request, paginate).configure(virtual_machine_table)
|
|
2149
2439
|
context["virtual_machine_table"] = virtual_machine_table
|
|
2150
|
-
|
|
2440
|
+
if ContentType.objects.get_for_model(VMInterface) in context["content_types"]:
|
|
2441
|
+
vm_interfaces = instance.vm_interfaces.restrict(request.user, "view")
|
|
2442
|
+
vminterface_table = VMInterfaceTable(vm_interfaces)
|
|
2443
|
+
vminterface_table.columns.hide("role")
|
|
2444
|
+
RequestConfig(request, paginate).configure(vminterface_table)
|
|
2445
|
+
context["vminterface_table"] = vminterface_table
|
|
2151
2446
|
if ContentType.objects.get_for_model(VLAN) in context["content_types"]:
|
|
2152
|
-
vlans = (
|
|
2153
|
-
instance.vlans.annotate(location_count=count_related(Location, "vlans"))
|
|
2154
|
-
.select_related(
|
|
2155
|
-
"vlan_group",
|
|
2156
|
-
"status",
|
|
2157
|
-
"tenant",
|
|
2158
|
-
)
|
|
2159
|
-
.restrict(request.user, "view")
|
|
2160
|
-
)
|
|
2447
|
+
vlans = instance.vlans.restrict(request.user, "view")
|
|
2161
2448
|
vlan_table = VLANTable(vlans)
|
|
2162
2449
|
vlan_table.columns.hide("role")
|
|
2163
2450
|
RequestConfig(request, paginate).configure(vlan_table)
|
|
2164
2451
|
context["vlan_table"] = vlan_table
|
|
2452
|
+
if ContentType.objects.get_for_model(Module) in context["content_types"]:
|
|
2453
|
+
modules = instance.modules.restrict(request.user, "view")
|
|
2454
|
+
module_table = ModuleTable(modules)
|
|
2455
|
+
module_table.columns.hide("role")
|
|
2456
|
+
RequestConfig(request, paginate).configure(module_table)
|
|
2457
|
+
context["module_table"] = module_table
|
|
2165
2458
|
return context
|
|
2166
2459
|
|
|
2167
2460
|
|
|
@@ -2360,6 +2653,153 @@ class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
2360
2653
|
table = tables.SecretsGroupTable
|
|
2361
2654
|
|
|
2362
2655
|
|
|
2656
|
+
#
|
|
2657
|
+
# Static Groups
|
|
2658
|
+
#
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
class StaticGroupAssociationUIViewSet(
|
|
2662
|
+
ObjectBulkDestroyViewMixin,
|
|
2663
|
+
ObjectChangeLogViewMixin,
|
|
2664
|
+
ObjectDestroyViewMixin,
|
|
2665
|
+
ObjectDetailViewMixin,
|
|
2666
|
+
ObjectListViewMixin,
|
|
2667
|
+
# TODO anything else?
|
|
2668
|
+
):
|
|
2669
|
+
filterset_class = filters.StaticGroupAssociationFilterSet
|
|
2670
|
+
filterset_form_class = forms.StaticGroupAssociationFilterForm
|
|
2671
|
+
queryset = StaticGroupAssociation.all_objects.all()
|
|
2672
|
+
serializer_class = serializers.StaticGroupAssociationSerializer
|
|
2673
|
+
table_class = tables.StaticGroupAssociationTable
|
|
2674
|
+
action_buttons = ("export",)
|
|
2675
|
+
|
|
2676
|
+
def alter_queryset(self, request):
|
|
2677
|
+
queryset = super().alter_queryset(request)
|
|
2678
|
+
# Default to only showing associations for static-type groups:
|
|
2679
|
+
if request is None or "dynamic_group" not in request.GET:
|
|
2680
|
+
queryset = queryset.filter(dynamic_group__group_type=DynamicGroupTypeChoices.TYPE_STATIC)
|
|
2681
|
+
return queryset
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
class DynamicGroupBulkAssignView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
2685
|
+
queryset = StaticGroupAssociation.objects.all()
|
|
2686
|
+
form_class = forms.DynamicGroupBulkAssignForm
|
|
2687
|
+
|
|
2688
|
+
def get_required_permission(self):
|
|
2689
|
+
return get_permission_for_model(self.queryset.model, "add")
|
|
2690
|
+
|
|
2691
|
+
def get(self, request):
|
|
2692
|
+
return redirect(self.get_return_url(request))
|
|
2693
|
+
|
|
2694
|
+
def post(self, request, **kwargs):
|
|
2695
|
+
"""
|
|
2696
|
+
Update the static group assignments of the provided `pk_list` (or `_all`) of the given `content_type`.
|
|
2697
|
+
|
|
2698
|
+
Unlike BulkEditView, this takes a single POST rather than two to perform its operation as
|
|
2699
|
+
there's no separate confirmation step involved.
|
|
2700
|
+
"""
|
|
2701
|
+
# TODO more error handling - content-type doesn't exist, model_class not found, filterset missing, etc.
|
|
2702
|
+
content_type = ContentType.objects.get(pk=request.POST.get("content_type"))
|
|
2703
|
+
model = content_type.model_class()
|
|
2704
|
+
self.default_return_url = get_route_for_model(model, "list")
|
|
2705
|
+
filterset_class = get_filterset_for_model(model)
|
|
2706
|
+
|
|
2707
|
+
if request.POST.get("_all"):
|
|
2708
|
+
if filterset_class is not None:
|
|
2709
|
+
pk_list = list(filterset_class(request.GET, model.objects.only("pk")).qs.values_list("pk", flat=True))
|
|
2710
|
+
else:
|
|
2711
|
+
pk_list = list(model.objects.all().values_list("pk", flat=True))
|
|
2712
|
+
else:
|
|
2713
|
+
pk_list = request.POST.getlist("pk")
|
|
2714
|
+
|
|
2715
|
+
form = self.form_class(model, request.POST)
|
|
2716
|
+
restrict_form_fields(form, request.user)
|
|
2717
|
+
|
|
2718
|
+
if form.is_valid():
|
|
2719
|
+
logger.debug("Form validation was successful")
|
|
2720
|
+
try:
|
|
2721
|
+
with transaction.atomic():
|
|
2722
|
+
add_to_groups = list(form.cleaned_data["add_to_groups"])
|
|
2723
|
+
new_group_name = form.cleaned_data["create_and_assign_to_new_group_name"]
|
|
2724
|
+
if new_group_name:
|
|
2725
|
+
if not request.user.has_perm("extras.add_dynamicgroup"):
|
|
2726
|
+
raise DynamicGroup.DoesNotExist
|
|
2727
|
+
else:
|
|
2728
|
+
new_group = DynamicGroup(
|
|
2729
|
+
name=new_group_name,
|
|
2730
|
+
content_type=content_type,
|
|
2731
|
+
group_type=DynamicGroupTypeChoices.TYPE_STATIC,
|
|
2732
|
+
)
|
|
2733
|
+
new_group.validated_save()
|
|
2734
|
+
# Check permissions
|
|
2735
|
+
DynamicGroup.objects.restrict(request.user, "add").get(pk=new_group.pk)
|
|
2736
|
+
|
|
2737
|
+
add_to_groups.append(new_group)
|
|
2738
|
+
msg = "Created dynamic group"
|
|
2739
|
+
logger.info(f"{msg} {new_group} (PK: {new_group.pk})")
|
|
2740
|
+
msg = format_html('{} <a href="{}">{}</a>', msg, new_group.get_absolute_url(), new_group)
|
|
2741
|
+
messages.success(self.request, msg)
|
|
2742
|
+
|
|
2743
|
+
with deferred_change_logging_for_bulk_operation():
|
|
2744
|
+
associations = []
|
|
2745
|
+
for pk in pk_list:
|
|
2746
|
+
for dynamic_group in add_to_groups:
|
|
2747
|
+
association, created = StaticGroupAssociation.objects.get_or_create(
|
|
2748
|
+
dynamic_group=dynamic_group,
|
|
2749
|
+
associated_object_type_id=content_type.id,
|
|
2750
|
+
associated_object_id=pk,
|
|
2751
|
+
)
|
|
2752
|
+
association.validated_save()
|
|
2753
|
+
associations.append(association)
|
|
2754
|
+
if created:
|
|
2755
|
+
logger.debug("Created %s", association)
|
|
2756
|
+
|
|
2757
|
+
# Enforce object-level permissions
|
|
2758
|
+
if self.queryset.filter(pk__in=[assoc.pk for assoc in associations]).count() != len(
|
|
2759
|
+
associations
|
|
2760
|
+
):
|
|
2761
|
+
raise StaticGroupAssociation.DoesNotExist
|
|
2762
|
+
|
|
2763
|
+
if associations:
|
|
2764
|
+
msg = (
|
|
2765
|
+
f"Added {len(pk_list)} {model._meta.verbose_name_plural} "
|
|
2766
|
+
f"to {len(add_to_groups)} dynamic group(s)."
|
|
2767
|
+
)
|
|
2768
|
+
logger.info(msg)
|
|
2769
|
+
messages.success(self.request, msg)
|
|
2770
|
+
|
|
2771
|
+
if form.cleaned_data["remove_from_groups"]:
|
|
2772
|
+
for dynamic_group in form.cleaned_data["remove_from_groups"]:
|
|
2773
|
+
(
|
|
2774
|
+
StaticGroupAssociation.objects.restrict(request.user, "delete")
|
|
2775
|
+
.filter(
|
|
2776
|
+
dynamic_group=dynamic_group,
|
|
2777
|
+
associated_object_type=content_type,
|
|
2778
|
+
associated_object_id__in=pk_list,
|
|
2779
|
+
)
|
|
2780
|
+
.delete()
|
|
2781
|
+
)
|
|
2782
|
+
|
|
2783
|
+
msg = (
|
|
2784
|
+
f"Removed {len(pk_list)} {model._meta.verbose_name_plural} from "
|
|
2785
|
+
f"{len(form.cleaned_data['remove_from_groups'])} dynamic group(s)."
|
|
2786
|
+
)
|
|
2787
|
+
logger.info(msg)
|
|
2788
|
+
messages.success(self.request, msg)
|
|
2789
|
+
except ValidationError as e:
|
|
2790
|
+
messages.error(self.request, e)
|
|
2791
|
+
except ObjectDoesNotExist:
|
|
2792
|
+
msg = "Static group association failed due to object-level permissions violation"
|
|
2793
|
+
logger.warning(msg)
|
|
2794
|
+
messages.error(self.request, msg)
|
|
2795
|
+
|
|
2796
|
+
else:
|
|
2797
|
+
logger.debug("Form validation failed")
|
|
2798
|
+
messages.error(self.request, form.errors)
|
|
2799
|
+
|
|
2800
|
+
return redirect(self.get_return_url(request))
|
|
2801
|
+
|
|
2802
|
+
|
|
2363
2803
|
#
|
|
2364
2804
|
# Custom statuses
|
|
2365
2805
|
#
|