nautobot 2.2.9__py3-none-any.whl → 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +113 -0
- nautobot/cloud/filters.py +187 -0
- nautobot/cloud/forms.py +339 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +246 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +157 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +125 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +95 -13
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +11 -3
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/schema.py +26 -4
- nautobot/core/jobs/__init__.py +16 -2
- nautobot/core/jobs/cleanup.py +100 -0
- nautobot/core/jobs/groups.py +38 -0
- nautobot/core/management/commands/generate_test_data.py +116 -3
- nautobot/core/models/__init__.py +34 -9
- nautobot/core/models/generics.py +19 -3
- nautobot/core/models/name_color_content_types.py +7 -28
- nautobot/core/models/querysets.py +4 -3
- nautobot/core/models/tree_queries.py +1 -1
- nautobot/core/models/utils.py +21 -5
- nautobot/core/settings.py +2 -17
- nautobot/core/settings.yaml +34 -13
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +130 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +13 -8
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +63 -14
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
- nautobot/core/tests/integration/test_swagger.py +1 -1
- nautobot/core/tests/nautobot_config.py +0 -1
- nautobot/core/tests/runner.py +2 -2
- nautobot/core/tests/test_api.py +1 -0
- nautobot/core/tests/test_authentication.py +7 -2
- nautobot/core/tests/test_filters.py +11 -9
- nautobot/core/tests/test_forms.py +9 -0
- nautobot/core/tests/test_graphql.py +27 -16
- nautobot/core/tests/test_jobs.py +122 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +119 -67
- nautobot/core/views/mixins.py +105 -36
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +81 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +252 -18
- nautobot/dcim/filters/__init__.py +373 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +834 -121
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +862 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +275 -99
- nautobot/dcim/models/devices.py +468 -13
- nautobot/dcim/models/racks.py +0 -1
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +229 -43
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +14 -6
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +693 -208
- nautobot/dcim/tests/test_filters.py +843 -217
- nautobot/dcim/tests/test_models.py +1072 -8
- nautobot/dcim/tests/test_views.py +1510 -341
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1106 -116
- nautobot/extras/__init__.py +0 -1
- nautobot/extras/api/serializers.py +115 -3
- nautobot/extras/api/urls.py +12 -0
- nautobot/extras/api/views.py +66 -0
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +460 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +24 -5
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +14 -1
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
- nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
- nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
- nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
- nautobot/extras/migrations/0111_metadata.py +162 -0
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
- nautobot/extras/migrations/0113_saved_views.py +77 -0
- nautobot/extras/models/__init__.py +15 -1
- nautobot/extras/models/change_logging.py +3 -3
- nautobot/extras/models/contacts.py +4 -0
- nautobot/extras/models/customfields.py +18 -3
- nautobot/extras/models/groups.py +389 -225
- nautobot/extras/models/jobs.py +6 -3
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +118 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +95 -100
- nautobot/extras/tables.py +165 -12
- nautobot/extras/templates/extras/dynamicgroup.html +44 -15
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
- nautobot/extras/templates/extras/job.html +1 -1
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +509 -23
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +6 -6
- nautobot/extras/tests/test_models.py +501 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +565 -8
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +51 -11
- nautobot/extras/views.py +542 -76
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +13 -0
- nautobot/ipam/forms.py +8 -4
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +11 -8
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +5 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +6 -0
- nautobot/ipam/tests/test_querysets.py +1 -1
- nautobot/ipam/tests/test_views.py +3 -2
- nautobot/ipam/urls.py +2 -2
- nautobot/ipam/views.py +18 -26
- nautobot/project-static/css/base.css +20 -0
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +892 -88
- nautobot/project-static/docs/apps/index.html +892 -88
- nautobot/project-static/docs/apps/nautobot-apps.html +892 -88
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +919 -120
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +904 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1618 -903
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +935 -144
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +977 -188
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +901 -99
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +897 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +991 -193
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +974 -131
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1078 -272
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1242 -334
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1727 -875
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1164 -381
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2088 -1374
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2246 -1422
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +912 -111
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +963 -163
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1010 -223
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1913 -1277
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1846 -1102
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +904 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2331 -1699
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1802 -1024
- nautobot/project-static/docs/development/apps/api/configuration-view.html +892 -88
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/global-search.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/graphql.html +892 -88
- nautobot/project-static/docs/development/apps/api/models/index.html +942 -90
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +892 -88
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +892 -88
- nautobot/project-static/docs/development/apps/api/prometheus.html +892 -88
- nautobot/project-static/docs/development/apps/api/setup.html +892 -88
- nautobot/project-static/docs/development/apps/api/testing.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +892 -88
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/base-template.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/index.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/notes.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +892 -88
- nautobot/project-static/docs/development/apps/api/views/urls.html +892 -88
- nautobot/project-static/docs/development/apps/index.html +892 -88
- nautobot/project-static/docs/development/apps/migration/code-updates.html +892 -88
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +892 -88
- nautobot/project-static/docs/development/apps/migration/from-v1.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +892 -88
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +892 -88
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +892 -88
- nautobot/project-static/docs/development/core/application-registry.html +892 -88
- nautobot/project-static/docs/development/core/best-practices.html +893 -88
- nautobot/project-static/docs/development/core/bootstrap-ui.html +892 -88
- nautobot/project-static/docs/development/core/caching.html +892 -88
- nautobot/project-static/docs/development/core/controllers.html +892 -88
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +892 -88
- nautobot/project-static/docs/development/core/generic-views.html +892 -88
- nautobot/project-static/docs/development/core/getting-started.html +892 -88
- nautobot/project-static/docs/development/core/homepage.html +892 -88
- nautobot/project-static/docs/development/core/index.html +892 -88
- nautobot/project-static/docs/development/core/model-checklist.html +901 -89
- nautobot/project-static/docs/development/core/model-features.html +892 -88
- nautobot/project-static/docs/development/core/natural-keys.html +892 -88
- nautobot/project-static/docs/development/core/navigation-menu.html +892 -88
- nautobot/project-static/docs/development/core/release-checklist.html +895 -91
- nautobot/project-static/docs/development/core/role-internals.html +892 -88
- nautobot/project-static/docs/development/core/settings.html +892 -88
- nautobot/project-static/docs/development/core/style-guide.html +893 -89
- nautobot/project-static/docs/development/core/templates.html +904 -89
- nautobot/project-static/docs/development/core/testing.html +892 -88
- nautobot/project-static/docs/development/core/user-preferences.html +892 -88
- nautobot/project-static/docs/development/index.html +892 -88
- nautobot/project-static/docs/development/jobs/index.html +893 -89
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +892 -88
- nautobot/project-static/docs/index.html +892 -88
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
- nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
- nautobot/project-static/docs/models/dcim/module.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
- nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
- nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
- nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
- nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
- nautobot/project-static/docs/models/extras/role.html +15 -0
- nautobot/project-static/docs/models/extras/savedview.html +15 -0
- nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
- nautobot/project-static/docs/models/extras/status.html +15 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +900 -89
- nautobot/project-static/docs/overview/design_philosophy.html +892 -88
- nautobot/project-static/docs/release-notes/index.html +1129 -92
- nautobot/project-static/docs/release-notes/version-1.0.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.1.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.2.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.3.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.4.html +892 -88
- nautobot/project-static/docs/release-notes/version-1.5.html +893 -89
- nautobot/project-static/docs/release-notes/version-1.6.html +893 -89
- nautobot/project-static/docs/release-notes/version-2.0.html +892 -88
- nautobot/project-static/docs/release-notes/version-2.1.html +892 -88
- nautobot/project-static/docs/release-notes/version-2.2.html +895 -91
- nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
- nautobot/project-static/docs/requirements.txt +5 -5
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +331 -256
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +992 -174
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +892 -88
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +896 -88
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +892 -88
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +944 -153
- nautobot/project-static/docs/user-guide/administration/installation/index.html +901 -93
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +934 -122
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +954 -157
- nautobot/project-static/docs/user-guide/administration/installation/services.html +913 -112
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +908 -99
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +892 -88
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +892 -88
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +892 -88
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +892 -88
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +893 -89
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +892 -88
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +893 -89
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +896 -88
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +895 -91
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +923 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +913 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +920 -116
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +914 -105
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +926 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +936 -118
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +928 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +937 -119
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +923 -115
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +916 -112
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +940 -83
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +943 -86
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +921 -103
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +929 -125
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +918 -114
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +922 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +924 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +906 -102
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +936 -88
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +897 -89
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +897 -89
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +901 -96
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +892 -88
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +897 -89
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +892 -88
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +892 -88
- nautobot/project-static/docs/user-guide/index.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1258 -785
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +896 -88
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +895 -91
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +950 -121
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +892 -88
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +892 -88
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +24 -20
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/models.py +0 -2
- nautobot/virtualization/tables.py +10 -3
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/METADATA +20 -18
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/RECORD +677 -557
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
nautobot/extras/views.py
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from urllib.parse import parse_qs
|
|
2
3
|
|
|
3
4
|
from celery import chain
|
|
4
5
|
from django.contrib import messages
|
|
6
|
+
from django.contrib.auth.models import AnonymousUser
|
|
5
7
|
from django.contrib.contenttypes.models import ContentType
|
|
6
8
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
7
|
-
from django.db import transaction
|
|
9
|
+
from django.db import IntegrityError, transaction
|
|
8
10
|
from django.db.models import ProtectedError, Q
|
|
9
11
|
from django.forms.utils import pretty_name
|
|
10
12
|
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|
11
13
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
12
14
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
13
15
|
from django.urls import reverse
|
|
16
|
+
from django.urls.exceptions import NoReverseMatch
|
|
14
17
|
from django.utils import timezone
|
|
15
18
|
from django.utils.encoding import iri_to_uri
|
|
16
19
|
from django.utils.html import format_html
|
|
@@ -18,38 +21,51 @@ from django.utils.http import url_has_allowed_host_and_scheme
|
|
|
18
21
|
from django.views.generic import View
|
|
19
22
|
from django_tables2 import RequestConfig
|
|
20
23
|
from jsonschema.validators import Draft7Validator
|
|
24
|
+
from rest_framework.decorators import action
|
|
21
25
|
|
|
22
26
|
from nautobot.core.forms import restrict_form_fields
|
|
23
27
|
from nautobot.core.models.querysets import count_related
|
|
24
28
|
from nautobot.core.models.utils import pretty_print_query
|
|
25
29
|
from nautobot.core.tables import ButtonsColumn
|
|
26
|
-
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
|
|
27
38
|
from nautobot.core.utils.requests import normalize_querydict
|
|
28
39
|
from nautobot.core.views import generic, viewsets
|
|
29
40
|
from nautobot.core.views.mixins import (
|
|
41
|
+
GetReturnURLMixin,
|
|
30
42
|
ObjectBulkDestroyViewMixin,
|
|
31
43
|
ObjectBulkUpdateViewMixin,
|
|
44
|
+
ObjectChangeLogViewMixin,
|
|
32
45
|
ObjectDestroyViewMixin,
|
|
46
|
+
ObjectDetailViewMixin,
|
|
33
47
|
ObjectEditViewMixin,
|
|
48
|
+
ObjectListViewMixin,
|
|
34
49
|
ObjectPermissionRequiredMixin,
|
|
35
50
|
)
|
|
36
51
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
37
52
|
from nautobot.core.views.utils import prepare_cloned_fields
|
|
38
53
|
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
|
|
54
|
+
from nautobot.dcim.models import Controller, Device, Interface, Module, Rack
|
|
55
|
+
from nautobot.dcim.tables import ControllerTable, DeviceTable, InterfaceTable, ModuleTable, RackTable
|
|
41
56
|
from nautobot.extras.constants import JOB_OVERRIDABLE_FIELDS
|
|
57
|
+
from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
|
|
42
58
|
from nautobot.extras.signals import change_context_state
|
|
43
59
|
from nautobot.extras.tasks import delete_custom_field_data
|
|
44
60
|
from nautobot.extras.utils import get_base_template, get_worker_count
|
|
45
61
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
46
62
|
from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
|
|
47
63
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
48
|
-
from nautobot.virtualization.tables import VirtualMachineTable
|
|
64
|
+
from nautobot.virtualization.tables import VirtualMachineTable, VMInterfaceTable
|
|
49
65
|
|
|
50
66
|
from . import filters, forms, tables
|
|
51
67
|
from .api import serializers
|
|
52
|
-
from .choices import JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
68
|
+
from .choices import DynamicGroupTypeChoices, JobExecutionType, JobResultStatusChoices, LogLevelChoices
|
|
53
69
|
from .datasources import (
|
|
54
70
|
enqueue_git_repository_diff_origin_and_local,
|
|
55
71
|
enqueue_pull_git_repository_and_refresh_data,
|
|
@@ -75,19 +91,24 @@ from .models import (
|
|
|
75
91
|
JobHook,
|
|
76
92
|
JobLogEntry,
|
|
77
93
|
JobResult,
|
|
94
|
+
MetadataType,
|
|
78
95
|
Note,
|
|
79
96
|
ObjectChange,
|
|
97
|
+
ObjectMetadata,
|
|
80
98
|
Relationship,
|
|
81
99
|
RelationshipAssociation,
|
|
82
100
|
Role,
|
|
101
|
+
SavedView,
|
|
83
102
|
ScheduledJob,
|
|
84
103
|
Secret,
|
|
85
104
|
SecretsGroup,
|
|
86
105
|
SecretsGroupAssociation,
|
|
106
|
+
StaticGroupAssociation,
|
|
87
107
|
Status,
|
|
88
108
|
Tag,
|
|
89
109
|
TaggedItem,
|
|
90
110
|
Team,
|
|
111
|
+
UserSavedViewAssociation,
|
|
91
112
|
Webhook,
|
|
92
113
|
)
|
|
93
114
|
from .registry import registry
|
|
@@ -698,12 +719,19 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
698
719
|
|
|
699
720
|
def get_extra_context(self, request, instance):
|
|
700
721
|
context = super().get_extra_context(request, instance)
|
|
701
|
-
model = instance.
|
|
722
|
+
model = instance.model
|
|
702
723
|
table_class = get_table_for_model(model)
|
|
703
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
|
+
|
|
704
732
|
if table_class is not None:
|
|
705
733
|
# Members table (for display on Members nav tab)
|
|
706
|
-
members_table = table_class(
|
|
734
|
+
members_table = table_class(members.restrict(request.user, "view"), orderable=False)
|
|
707
735
|
paginate = {
|
|
708
736
|
"paginator_class": EnhancedPaginator,
|
|
709
737
|
"per_page": get_paginate_count(request),
|
|
@@ -723,7 +751,16 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
723
751
|
ancestors_table = tables.NestedDynamicGroupAncestorsTable(ancestors, orderable=False)
|
|
724
752
|
ancestors_tree = instance.flatten_ancestors_tree(instance.ancestors_tree())
|
|
725
753
|
|
|
726
|
-
|
|
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
|
|
727
764
|
context["members_table"] = members_table
|
|
728
765
|
context["ancestors_table"] = ancestors_table
|
|
729
766
|
context["ancestors_tree"] = ancestors_tree
|
|
@@ -775,16 +812,19 @@ class DynamicGroupEditView(generic.ObjectEditView):
|
|
|
775
812
|
# Obtain the instance, but do not yet `save()` it to the database.
|
|
776
813
|
obj = form.save(commit=False)
|
|
777
814
|
|
|
778
|
-
# Process the filter form and save the query filters to `obj.filter`.
|
|
779
815
|
ctx = self.get_extra_context(request, obj)
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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)
|
|
785
823
|
|
|
786
824
|
# After filters have been set, now we save the object to the database.
|
|
787
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()
|
|
788
828
|
# Check that the new object conforms with any assigned object-level permissions
|
|
789
829
|
self.queryset.get(pk=obj.pk)
|
|
790
830
|
|
|
@@ -867,6 +907,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
867
907
|
filterset = filters.DynamicGroupFilterSet
|
|
868
908
|
|
|
869
909
|
|
|
910
|
+
# 3.0 TODO: remove, deprecated since 2.3 (#5845)
|
|
870
911
|
class ObjectDynamicGroupsView(generic.GenericView):
|
|
871
912
|
"""
|
|
872
913
|
Present a list of dynamic groups associated to a particular object.
|
|
@@ -883,16 +924,18 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
883
924
|
obj = get_object_or_404(model, **kwargs)
|
|
884
925
|
|
|
885
926
|
# Gather all dynamic groups for this object (and its related objects)
|
|
886
|
-
|
|
887
|
-
data=obj.
|
|
927
|
+
dynamicgroups_table = tables.DynamicGroupTable(
|
|
928
|
+
data=obj.dynamic_groups.restrict(request.user, "view"), orderable=False
|
|
888
929
|
)
|
|
930
|
+
dynamicgroups_table.columns.hide("content_type")
|
|
931
|
+
dynamicgroups_table.columns.hide("members")
|
|
889
932
|
|
|
890
933
|
# Apply the request context
|
|
891
934
|
paginate = {
|
|
892
935
|
"paginator_class": EnhancedPaginator,
|
|
893
936
|
"per_page": get_paginate_count(request),
|
|
894
937
|
}
|
|
895
|
-
RequestConfig(request, paginate).configure(
|
|
938
|
+
RequestConfig(request, paginate).configure(dynamicgroups_table)
|
|
896
939
|
|
|
897
940
|
self.base_template = get_base_template(self.base_template, model)
|
|
898
941
|
|
|
@@ -903,7 +946,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
903
946
|
"object": obj,
|
|
904
947
|
"verbose_name": obj._meta.verbose_name,
|
|
905
948
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
906
|
-
"table":
|
|
949
|
+
"table": dynamicgroups_table,
|
|
907
950
|
"base_template": self.base_template,
|
|
908
951
|
"active_tab": "dynamic-groups",
|
|
909
952
|
},
|
|
@@ -1583,6 +1626,257 @@ class JobApprovalRequestView(generic.ObjectView):
|
|
|
1583
1626
|
)
|
|
1584
1627
|
|
|
1585
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
|
+
|
|
1586
1880
|
class ScheduledJobListView(generic.ObjectListView):
|
|
1587
1881
|
queryset = ScheduledJob.objects.enabled()
|
|
1588
1882
|
table = tables.ScheduledJobTable
|
|
@@ -1882,6 +2176,58 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
1882
2176
|
)
|
|
1883
2177
|
|
|
1884
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
|
+
|
|
1885
2231
|
#
|
|
1886
2232
|
# Notes
|
|
1887
2233
|
#
|
|
@@ -2041,43 +2387,32 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2041
2387
|
}
|
|
2042
2388
|
|
|
2043
2389
|
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")
|
|
2390
|
+
devices = instance.devices.restrict(request.user, "view")
|
|
2052
2391
|
device_table = DeviceTable(devices)
|
|
2053
2392
|
device_table.columns.hide("role")
|
|
2054
2393
|
RequestConfig(request, paginate).configure(device_table)
|
|
2055
2394
|
context["device_table"] = device_table
|
|
2056
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
|
+
|
|
2057
2403
|
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")
|
|
2404
|
+
controllers = instance.controllers.restrict(request.user, "view")
|
|
2064
2405
|
controller_table = ControllerTable(controllers)
|
|
2065
2406
|
controller_table.columns.hide("role")
|
|
2066
2407
|
RequestConfig(request, paginate).configure(controller_table)
|
|
2067
2408
|
context["controller_table"] = controller_table
|
|
2068
2409
|
|
|
2069
2410
|
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
|
-
)
|
|
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),
|
|
2081
2416
|
)
|
|
2082
2417
|
ipaddress_table = IPAddressTable(ipaddress)
|
|
2083
2418
|
ipaddress_table.columns.hide("role")
|
|
@@ -2085,57 +2420,41 @@ class RoleUIViewSet(viewsets.NautobotUIViewSet):
|
|
|
2085
2420
|
context["ipaddress_table"] = ipaddress_table
|
|
2086
2421
|
|
|
2087
2422
|
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
|
-
)
|
|
2423
|
+
prefixes = instance.prefixes.restrict(request.user, "view")
|
|
2098
2424
|
prefix_table = PrefixTable(prefixes)
|
|
2099
2425
|
prefix_table.columns.hide("role")
|
|
2100
2426
|
RequestConfig(request, paginate).configure(prefix_table)
|
|
2101
2427
|
context["prefix_table"] = prefix_table
|
|
2102
2428
|
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")
|
|
2429
|
+
racks = instance.racks.restrict(request.user, "view")
|
|
2109
2430
|
rack_table = RackTable(racks)
|
|
2110
2431
|
rack_table.columns.hide("role")
|
|
2111
2432
|
RequestConfig(request, paginate).configure(rack_table)
|
|
2112
2433
|
context["rack_table"] = rack_table
|
|
2113
2434
|
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")
|
|
2435
|
+
virtual_machines = instance.virtual_machines.restrict(request.user, "view")
|
|
2120
2436
|
virtual_machine_table = VirtualMachineTable(virtual_machines)
|
|
2121
2437
|
virtual_machine_table.columns.hide("role")
|
|
2122
2438
|
RequestConfig(request, paginate).configure(virtual_machine_table)
|
|
2123
2439
|
context["virtual_machine_table"] = virtual_machine_table
|
|
2124
|
-
|
|
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
|
|
2125
2446
|
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
|
-
)
|
|
2447
|
+
vlans = instance.vlans.restrict(request.user, "view")
|
|
2135
2448
|
vlan_table = VLANTable(vlans)
|
|
2136
2449
|
vlan_table.columns.hide("role")
|
|
2137
2450
|
RequestConfig(request, paginate).configure(vlan_table)
|
|
2138
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
|
|
2139
2458
|
return context
|
|
2140
2459
|
|
|
2141
2460
|
|
|
@@ -2334,6 +2653,153 @@ class SecretsGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
2334
2653
|
table = tables.SecretsGroupTable
|
|
2335
2654
|
|
|
2336
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
|
+
|
|
2337
2803
|
#
|
|
2338
2804
|
# Custom statuses
|
|
2339
2805
|
#
|