nautobot 2.2.9__py3-none-any.whl → 2.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/forms.py +4 -0
- nautobot/apps/models.py +10 -1
- nautobot/circuits/__init__.py +0 -1
- nautobot/circuits/apps.py +1 -0
- nautobot/circuits/factory.py +15 -3
- nautobot/circuits/filters.py +13 -0
- nautobot/circuits/forms.py +13 -0
- nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
- nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
- nautobot/circuits/models.py +16 -3
- nautobot/circuits/tables.py +16 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
- nautobot/circuits/tests/test_api.py +7 -5
- nautobot/circuits/tests/test_filters.py +12 -5
- nautobot/circuits/tests/test_models.py +33 -2
- nautobot/circuits/views.py +2 -3
- nautobot/cloud/__init__.py +0 -0
- nautobot/cloud/api/__init__.py +0 -0
- nautobot/cloud/api/serializers.py +54 -0
- nautobot/cloud/api/urls.py +16 -0
- nautobot/cloud/api/views.py +48 -0
- nautobot/cloud/apps.py +13 -0
- nautobot/cloud/factory.py +111 -0
- nautobot/cloud/filters.py +184 -0
- nautobot/cloud/forms.py +333 -0
- nautobot/cloud/homepage.py +43 -0
- nautobot/cloud/migrations/0001_initial.py +304 -0
- nautobot/cloud/migrations/__init__.py +0 -0
- nautobot/cloud/models.py +247 -0
- nautobot/cloud/navigation.py +85 -0
- nautobot/cloud/tables.py +173 -0
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +128 -0
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +100 -0
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +65 -0
- nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
- nautobot/cloud/tests/__init__.py +0 -0
- nautobot/cloud/tests/test_api.py +248 -0
- nautobot/cloud/tests/test_filters.py +113 -0
- nautobot/cloud/tests/test_models.py +43 -0
- nautobot/cloud/tests/test_views.py +153 -0
- nautobot/cloud/urls.py +14 -0
- nautobot/cloud/views.py +181 -0
- nautobot/core/__init__.py +0 -3
- nautobot/core/api/metadata.py +1 -0
- nautobot/core/api/parsers.py +7 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/utils.py +1 -0
- nautobot/core/api/views.py +4 -0
- nautobot/core/apps/__init__.py +6 -3
- nautobot/core/constants.py +8 -0
- nautobot/core/factory.py +32 -1
- nautobot/core/filters.py +96 -28
- nautobot/core/forms/fields.py +10 -4
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +18 -1
- nautobot/core/graphql/generators.py +2 -2
- nautobot/core/graphql/schema.py +34 -4
- nautobot/core/jobs/__init__.py +17 -6
- nautobot/core/jobs/cleanup.py +100 -0
- nautobot/core/jobs/groups.py +38 -0
- nautobot/core/management/commands/generate_test_data.py +116 -3
- nautobot/core/models/__init__.py +34 -9
- nautobot/core/models/generics.py +19 -3
- nautobot/core/models/name_color_content_types.py +7 -28
- nautobot/core/models/querysets.py +4 -3
- nautobot/core/models/tree_queries.py +1 -1
- nautobot/core/models/utils.py +21 -5
- nautobot/core/settings.py +4 -30
- nautobot/core/settings.yaml +34 -27
- nautobot/core/settings_funcs.py +103 -0
- nautobot/core/tables.py +127 -56
- nautobot/core/templates/admin/search_form.html +1 -1
- nautobot/core/templates/buttons/add.html +11 -3
- nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
- nautobot/core/templates/buttons/export.html +101 -53
- nautobot/core/templates/buttons/job_import.html +11 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
- nautobot/core/templates/generic/object_bulk_update.html +3 -1
- nautobot/core/templates/generic/object_changelog.html +0 -9
- nautobot/core/templates/generic/object_list.html +156 -17
- nautobot/core/templates/generic/object_retrieve.html +80 -16
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +1 -0
- nautobot/core/templates/inc/paginator.html +7 -7
- nautobot/core/templates/inc/search_panel.html +2 -2
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +13 -23
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
- nautobot/core/templates/utilities/theme_preview.html +25 -8
- nautobot/core/templates/utilities/worker_status.html +152 -0
- nautobot/core/templatetags/buttons.py +335 -38
- nautobot/core/templatetags/form_helpers.py +1 -1
- nautobot/core/templatetags/helpers.py +181 -11
- nautobot/core/testing/api.py +5 -4
- nautobot/core/testing/filters.py +51 -13
- nautobot/core/testing/mixins.py +46 -0
- nautobot/core/testing/models.py +22 -0
- nautobot/core/testing/schema.py +4 -8
- nautobot/core/testing/views.py +31 -14
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
- nautobot/core/tests/integration/test_swagger.py +1 -1
- nautobot/core/tests/nautobot_config.py +0 -1
- nautobot/core/tests/runner.py +2 -2
- nautobot/core/tests/test_api.py +1 -0
- nautobot/core/tests/test_authentication.py +7 -2
- nautobot/core/tests/test_filters.py +11 -9
- nautobot/core/tests/test_forms.py +9 -0
- nautobot/core/tests/test_graphql.py +27 -16
- nautobot/core/tests/test_jobs.py +123 -74
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +12 -5
- nautobot/core/tests/test_utils.py +31 -20
- nautobot/core/tests/test_views.py +6 -6
- nautobot/core/urls.py +8 -3
- nautobot/core/utils/deprecation.py +29 -0
- nautobot/core/utils/filtering.py +12 -9
- nautobot/core/utils/lookup.py +37 -2
- nautobot/core/utils/requests.py +4 -1
- nautobot/core/views/__init__.py +137 -24
- nautobot/core/views/generic.py +118 -66
- nautobot/core/views/mixins.py +104 -35
- nautobot/core/views/paginator.py +9 -3
- nautobot/core/views/renderers.py +121 -56
- nautobot/core/views/utils.py +79 -1
- nautobot/dcim/__init__.py +0 -1
- nautobot/dcim/api/serializers.py +180 -44
- nautobot/dcim/api/urls.py +7 -3
- nautobot/dcim/api/views.py +53 -7
- nautobot/dcim/apps.py +3 -0
- nautobot/dcim/choices.py +25 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/factory.py +249 -18
- nautobot/dcim/filters/__init__.py +369 -193
- nautobot/dcim/filters/mixins.py +274 -1
- nautobot/dcim/forms.py +817 -109
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/homepage.py +1 -1
- nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
- nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
- nautobot/dcim/migrations/0061_module_models.py +861 -0
- nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
- nautobot/dcim/models/__init__.py +8 -0
- nautobot/dcim/models/cables.py +15 -0
- nautobot/dcim/models/device_component_templates.py +207 -53
- nautobot/dcim/models/device_components.py +275 -106
- nautobot/dcim/models/devices.py +466 -13
- nautobot/dcim/navigation.py +47 -0
- nautobot/dcim/signals.py +3 -3
- nautobot/dcim/tables/__init__.py +35 -23
- nautobot/dcim/tables/devices.py +231 -59
- nautobot/dcim/tables/devicetypes.py +65 -9
- nautobot/dcim/tables/racks.py +5 -1
- nautobot/dcim/tables/template_code.py +46 -26
- nautobot/dcim/templates/dcim/cable_connect.html +76 -3
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/device/base.html +15 -7
- nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
- nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
- nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
- nautobot/dcim/templates/dcim/device/frontports.html +2 -3
- nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
- nautobot/dcim/templates/dcim/device/inventory.html +2 -3
- nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
- nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
- nautobot/dcim/templates/dcim/device/powerports.html +2 -3
- nautobot/dcim/templates/dcim/device/rearports.html +2 -3
- nautobot/dcim/templates/dcim/device.html +45 -1
- nautobot/dcim/templates/dcim/device_component.html +13 -5
- nautobot/dcim/templates/dcim/device_list.html +2 -1
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -6
- nautobot/dcim/templates/dcim/devicetype.html +99 -98
- nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
- nautobot/dcim/templates/dcim/interface.html +17 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/manufacturer.html +24 -0
- nautobot/dcim/templates/dcim/module/base.html +97 -0
- nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
- nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
- nautobot/dcim/templates/dcim/module_destroy.html +5 -0
- nautobot/dcim/templates/dcim/module_frontports.html +53 -0
- nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
- nautobot/dcim/templates/dcim/module_list.html +20 -0
- nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
- nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
- nautobot/dcim/templates/dcim/module_powerports.html +53 -0
- nautobot/dcim/templates/dcim/module_rearports.html +53 -0
- nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
- nautobot/dcim/templates/dcim/module_update.html +71 -0
- nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
- nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
- nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +142 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
- nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
- nautobot/dcim/tests/test_api.py +691 -208
- nautobot/dcim/tests/test_filters.py +836 -217
- nautobot/dcim/tests/test_models.py +1072 -39
- nautobot/dcim/tests/test_views.py +1488 -358
- nautobot/dcim/urls.py +17 -2
- nautobot/dcim/utils.py +2 -3
- nautobot/dcim/views.py +1107 -120
- nautobot/extras/__init__.py +0 -1
- nautobot/extras/api/serializers.py +115 -3
- nautobot/extras/api/urls.py +12 -0
- nautobot/extras/api/views.py +125 -7
- nautobot/extras/apps.py +2 -2
- nautobot/extras/choices.py +43 -0
- nautobot/extras/context_managers.py +13 -8
- nautobot/extras/datasources/git.py +2 -0
- nautobot/extras/factory.py +422 -9
- nautobot/extras/filters/__init__.py +174 -3
- nautobot/extras/filters/mixins.py +46 -43
- nautobot/extras/forms/base.py +17 -4
- nautobot/extras/forms/forms.py +227 -8
- nautobot/extras/forms/mixins.py +93 -0
- nautobot/extras/graphql/types.py +23 -10
- nautobot/extras/homepage.py +16 -13
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
- nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
- nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
- nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
- nautobot/extras/migrations/0111_metadata.py +162 -0
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
- nautobot/extras/migrations/0113_saved_views.py +77 -0
- nautobot/extras/models/__init__.py +15 -1
- nautobot/extras/models/change_logging.py +3 -3
- nautobot/extras/models/contacts.py +4 -0
- nautobot/extras/models/customfields.py +18 -3
- nautobot/extras/models/groups.py +389 -225
- nautobot/extras/models/jobs.py +4 -84
- nautobot/extras/models/metadata.py +441 -0
- nautobot/extras/models/mixins.py +72 -62
- nautobot/extras/models/models.py +116 -9
- nautobot/extras/models/relationships.py +9 -2
- nautobot/extras/models/tags.py +13 -2
- nautobot/extras/navigation.py +57 -0
- nautobot/extras/plugins/__init__.py +3 -1
- nautobot/extras/querysets.py +30 -66
- nautobot/extras/signals.py +96 -114
- nautobot/extras/tables.py +171 -47
- nautobot/extras/templates/extras/dynamicgroup.html +44 -15
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
- nautobot/extras/templates/extras/job.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +0 -11
- nautobot/extras/templates/extras/jobresult.html +61 -74
- nautobot/extras/templates/extras/metadatatype_create.html +89 -0
- nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
- nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
- nautobot/extras/templates/extras/objectchange_list.html +0 -12
- nautobot/extras/templates/extras/plugins_list.html +1 -3
- nautobot/extras/templates/extras/role_retrieve.html +48 -0
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
- nautobot/extras/tests/integration/test_customfields.py +1 -0
- nautobot/extras/tests/test_api.py +501 -22
- nautobot/extras/tests/test_changelog.py +20 -9
- nautobot/extras/tests/test_context_managers.py +22 -15
- nautobot/extras/tests/test_datasources.py +13 -1
- nautobot/extras/tests/test_dynamicgroups.py +201 -171
- nautobot/extras/tests/test_filters.py +211 -12
- nautobot/extras/tests/test_jobs.py +4 -4
- nautobot/extras/tests/test_models.py +499 -4
- nautobot/extras/tests/test_relationships.py +1 -0
- nautobot/extras/tests/test_views.py +565 -28
- nautobot/extras/tests/test_webhooks.py +1 -1
- nautobot/extras/urls.py +5 -0
- nautobot/extras/utils.py +56 -45
- nautobot/extras/views.py +585 -96
- nautobot/ipam/__init__.py +0 -1
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/factory.py +17 -19
- nautobot/ipam/filters.py +14 -1
- nautobot/ipam/forms.py +9 -5
- nautobot/ipam/graphql/types.py +2 -2
- nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
- nautobot/ipam/models.py +23 -9
- nautobot/ipam/querysets.py +1 -1
- nautobot/ipam/signals.py +4 -2
- nautobot/ipam/tables.py +1 -0
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -0
- nautobot/ipam/tests/test_api.py +37 -18
- nautobot/ipam/tests/test_filters.py +26 -2
- nautobot/ipam/tests/test_models.py +8 -3
- nautobot/ipam/tests/test_querysets.py +1 -1
- nautobot/ipam/tests/test_views.py +3 -2
- nautobot/ipam/urls.py +2 -2
- nautobot/ipam/views.py +25 -28
- nautobot/project-static/css/base.css +20 -1
- nautobot/project-static/css/dark.css +11 -0
- nautobot/project-static/docs/404.html +884 -80
- nautobot/project-static/docs/apps/index.html +884 -80
- nautobot/project-static/docs/apps/nautobot-apps.html +884 -80
- nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +911 -112
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1457 -790
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +927 -136
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +969 -180
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +893 -91
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +889 -85
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +983 -185
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +938 -143
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1064 -274
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1190 -346
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1663 -865
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1156 -373
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2200 -1502
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2229 -1421
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +904 -103
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +955 -155
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1002 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1911 -1275
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1835 -1091
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +896 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2323 -1693
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1785 -1023
- nautobot/project-static/docs/development/apps/api/configuration-view.html +884 -80
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/global-search.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/graphql.html +884 -80
- nautobot/project-static/docs/development/apps/api/models/index.html +922 -81
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +884 -80
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +884 -80
- nautobot/project-static/docs/development/apps/api/prometheus.html +884 -80
- nautobot/project-static/docs/development/apps/api/setup.html +884 -80
- nautobot/project-static/docs/development/apps/api/testing.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +884 -80
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/base-template.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/index.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/notes.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +884 -80
- nautobot/project-static/docs/development/apps/api/views/urls.html +884 -80
- nautobot/project-static/docs/development/apps/index.html +884 -80
- nautobot/project-static/docs/development/apps/migration/code-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +884 -80
- nautobot/project-static/docs/development/apps/migration/from-v1.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +884 -80
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +884 -80
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +884 -80
- nautobot/project-static/docs/development/core/application-registry.html +884 -80
- nautobot/project-static/docs/development/core/best-practices.html +885 -80
- nautobot/project-static/docs/development/core/bootstrap-ui.html +884 -80
- nautobot/project-static/docs/development/core/caching.html +884 -80
- nautobot/project-static/docs/development/core/controllers.html +884 -80
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +884 -80
- nautobot/project-static/docs/development/core/generic-views.html +884 -80
- nautobot/project-static/docs/development/core/getting-started.html +884 -80
- nautobot/project-static/docs/development/core/homepage.html +884 -80
- nautobot/project-static/docs/development/core/index.html +884 -91
- nautobot/project-static/docs/development/core/model-checklist.html +887 -81
- nautobot/project-static/docs/development/core/model-features.html +884 -80
- nautobot/project-static/docs/development/core/natural-keys.html +884 -80
- nautobot/project-static/docs/development/core/navigation-menu.html +884 -80
- nautobot/project-static/docs/development/core/release-checklist.html +887 -83
- nautobot/project-static/docs/development/core/role-internals.html +884 -80
- nautobot/project-static/docs/development/core/settings.html +884 -80
- nautobot/project-static/docs/development/core/style-guide.html +885 -81
- nautobot/project-static/docs/development/core/templates.html +896 -81
- nautobot/project-static/docs/development/core/testing.html +884 -80
- nautobot/project-static/docs/development/core/user-preferences.html +884 -80
- nautobot/project-static/docs/development/index.html +884 -80
- nautobot/project-static/docs/development/jobs/index.html +1247 -457
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +884 -80
- nautobot/project-static/docs/index.html +13 -8228
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
- nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
- nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
- nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
- nautobot/project-static/docs/models/dcim/module.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
- nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
- nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
- nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
- nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
- nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
- nautobot/project-static/docs/models/extras/role.html +15 -0
- nautobot/project-static/docs/models/extras/savedview.html +15 -0
- nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
- nautobot/project-static/docs/models/extras/status.html +15 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +892 -81
- nautobot/project-static/docs/overview/design_philosophy.html +886 -82
- nautobot/project-static/docs/overview/index.html +9032 -13
- nautobot/project-static/docs/release-notes/index.html +887 -83
- nautobot/project-static/docs/release-notes/version-1.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.2.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.3.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.4.html +884 -80
- nautobot/project-static/docs/release-notes/version-1.5.html +885 -81
- nautobot/project-static/docs/release-notes/version-1.6.html +885 -81
- nautobot/project-static/docs/release-notes/version-2.0.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.1.html +884 -80
- nautobot/project-static/docs/release-notes/version-2.2.html +990 -323
- nautobot/project-static/docs/release-notes/version-2.3.html +9524 -0
- nautobot/project-static/docs/requirements.txt +4 -4
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +335 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +983 -197
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +884 -80
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +888 -84
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +884 -80
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/index.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation/services.html +888 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +900 -91
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +884 -80
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +884 -80
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +884 -80
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +915 -163
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +884 -80
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +885 -81
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +888 -80
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +887 -83
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +910 -92
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +915 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +905 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +912 -108
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +906 -97
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +918 -100
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +928 -110
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +920 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +929 -111
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +920 -102
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +913 -109
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +914 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +908 -104
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +932 -75
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +935 -78
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +913 -95
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +921 -117
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +910 -106
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +914 -96
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +916 -98
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +898 -94
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +889 -81
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +893 -88
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +884 -80
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +889 -81
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +884 -80
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +884 -80
- nautobot/project-static/docs/user-guide/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1250 -777
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +888 -80
- nautobot/project-static/docs/user-guide/platform-functionality/metadata.html +8948 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +887 -83
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +942 -113
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +884 -80
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +884 -80
- nautobot/project-static/js/forms.js +71 -0
- nautobot/project-static/js/table_sorting_indicator.js +46 -0
- nautobot/project-static/js/tableconfig.js +6 -1
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
- nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/tenancy/__init__.py +0 -1
- nautobot/tenancy/apps.py +1 -0
- nautobot/tenancy/factory.py +3 -2
- nautobot/tenancy/filters/__init__.py +1 -0
- nautobot/tenancy/forms.py +1 -1
- nautobot/tenancy/templates/tenancy/tenant.html +22 -18
- nautobot/tenancy/views.py +11 -10
- nautobot/users/__init__.py +0 -1
- nautobot/users/api/serializers.py +1 -1
- nautobot/users/api/views.py +4 -2
- nautobot/users/apps.py +3 -2
- nautobot/users/factory.py +3 -3
- nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
- nautobot/users/models.py +12 -0
- nautobot/users/tests/test_filters.py +6 -3
- nautobot/users/urls.py +8 -0
- nautobot/virtualization/__init__.py +0 -1
- nautobot/virtualization/apps.py +1 -0
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/forms.py +11 -3
- nautobot/virtualization/graphql/types.py +2 -2
- nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
- nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
- nautobot/virtualization/tables.py +15 -5
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
- nautobot/virtualization/tests/test_api.py +9 -4
- nautobot/virtualization/tests/test_filters.py +22 -0
- nautobot/virtualization/tests/test_models.py +7 -3
- nautobot/virtualization/tests/test_views.py +19 -3
- nautobot/virtualization/urls.py +2 -2
- nautobot/virtualization/views.py +10 -32
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/METADATA +21 -19
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/RECORD +679 -559
- nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
- nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
- nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
- nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
- nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
- nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
- nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
- nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
- nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
- /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/NOTICE +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/WHEEL +0 -0
- {nautobot-2.2.9.dist-info → nautobot-2.3.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import random
|
|
2
|
-
import time
|
|
3
|
-
from unittest.mock import patch
|
|
4
2
|
|
|
5
3
|
from django.contrib.contenttypes.models import ContentType
|
|
6
|
-
from django.core.cache import cache
|
|
7
4
|
from django.core.exceptions import ValidationError
|
|
8
|
-
from django.db.models import ProtectedError
|
|
9
|
-
from django.test import override_settings
|
|
5
|
+
from django.db.models import ProtectedError, QuerySet
|
|
10
6
|
from django.urls import reverse
|
|
11
7
|
|
|
12
8
|
from nautobot.core.forms.fields import MultiMatchModelMultipleChoiceField, MultiValueCharField
|
|
13
9
|
from nautobot.core.forms.widgets import APISelectMultiple, MultiValueCharInput
|
|
14
10
|
from nautobot.core.testing import TestCase
|
|
11
|
+
from nautobot.core.testing.filters import FilterTestCases
|
|
15
12
|
from nautobot.dcim.choices import PortTypeChoices
|
|
16
13
|
from nautobot.dcim.filters import DeviceFilterSet
|
|
17
14
|
from nautobot.dcim.forms import DeviceFilterForm, DeviceForm
|
|
@@ -29,6 +26,7 @@ from nautobot.extras.choices import (
|
|
|
29
26
|
CustomFieldFilterLogicChoices,
|
|
30
27
|
CustomFieldTypeChoices,
|
|
31
28
|
DynamicGroupOperatorChoices,
|
|
29
|
+
DynamicGroupTypeChoices,
|
|
32
30
|
RelationshipTypeChoices,
|
|
33
31
|
)
|
|
34
32
|
from nautobot.extras.filters import DynamicGroupFilterSet, DynamicGroupMembershipFilterSet
|
|
@@ -42,8 +40,10 @@ from nautobot.extras.models import (
|
|
|
42
40
|
Status,
|
|
43
41
|
Tag,
|
|
44
42
|
)
|
|
45
|
-
from nautobot.ipam.models import Prefix
|
|
43
|
+
from nautobot.ipam.models import IPAddress, Prefix
|
|
44
|
+
from nautobot.ipam.querysets import PrefixQuerySet
|
|
46
45
|
from nautobot.tenancy.models import Tenant
|
|
46
|
+
from nautobot.virtualization.models import VirtualMachine
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class DynamicGroupTestBase(TestCase):
|
|
@@ -64,7 +64,9 @@ class DynamicGroupTestBase(TestCase):
|
|
|
64
64
|
]
|
|
65
65
|
|
|
66
66
|
cls.manufacturer = Manufacturer.objects.first()
|
|
67
|
-
cls.device_type = DeviceType.objects.
|
|
67
|
+
cls.device_type = DeviceType.objects.create(
|
|
68
|
+
manufacturer=cls.manufacturer, model="Test Dynamic Groups Device Type"
|
|
69
|
+
)
|
|
68
70
|
cls.device_role = Role.objects.get_for_model(Device).first()
|
|
69
71
|
statuses = Status.objects.get_for_model(Device)
|
|
70
72
|
cls.status_1 = statuses[0]
|
|
@@ -107,7 +109,7 @@ class DynamicGroupTestBase(TestCase):
|
|
|
107
109
|
DynamicGroup.objects.create(
|
|
108
110
|
name="Parent",
|
|
109
111
|
description="The parent group with no filter",
|
|
110
|
-
|
|
112
|
+
group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_SET,
|
|
111
113
|
content_type=cls.device_ct,
|
|
112
114
|
),
|
|
113
115
|
# Location-1 only
|
|
@@ -128,7 +130,7 @@ class DynamicGroupTestBase(TestCase):
|
|
|
128
130
|
DynamicGroup.objects.create(
|
|
129
131
|
name="Third Child",
|
|
130
132
|
description="A third child group with a child of its own",
|
|
131
|
-
|
|
133
|
+
group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_SET,
|
|
132
134
|
content_type=cls.device_ct,
|
|
133
135
|
),
|
|
134
136
|
# Nested child of third-child to test ancestors/descendants
|
|
@@ -167,6 +169,7 @@ class DynamicGroupTestBase(TestCase):
|
|
|
167
169
|
filter={"platform": ["invalidvalue"]},
|
|
168
170
|
content_type=cls.device_ct,
|
|
169
171
|
)
|
|
172
|
+
cls.groups.append(cls.invalid_filter)
|
|
170
173
|
|
|
171
174
|
# Setup the group membership hiearchy to use for graph testing
|
|
172
175
|
cls.memberships = [
|
|
@@ -196,13 +199,6 @@ class DynamicGroupTestBase(TestCase):
|
|
|
196
199
|
),
|
|
197
200
|
]
|
|
198
201
|
|
|
199
|
-
def assertQuerySetEqual(self, left_qs, right_qs):
|
|
200
|
-
"""Compare two querysets and assert that they are equal."""
|
|
201
|
-
self.assertEqual(
|
|
202
|
-
sorted(left_qs.values_list("pk", flat=True)),
|
|
203
|
-
sorted(right_qs.values_list("pk", flat=True)),
|
|
204
|
-
)
|
|
205
|
-
|
|
206
202
|
|
|
207
203
|
class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mixin?
|
|
208
204
|
"""DynamicGroup model tests."""
|
|
@@ -250,19 +246,22 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
250
246
|
|
|
251
247
|
def test_full_clean_valid(self):
|
|
252
248
|
"""Test a clean validation."""
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
old_filter =
|
|
249
|
+
set_group = self.groups[0]
|
|
250
|
+
filter_group = self.groups[1]
|
|
251
|
+
old_filter = filter_group.filter
|
|
256
252
|
|
|
257
253
|
# Overload the filter and validate that it is the same afterward.
|
|
258
254
|
new_filter = {"has_interfaces": True}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
with self.assertRaises(ValidationError):
|
|
256
|
+
set_group.set_filter(new_filter)
|
|
257
|
+
set_group.validated_save()
|
|
258
|
+
filter_group.set_filter(new_filter)
|
|
259
|
+
filter_group.validated_save()
|
|
260
|
+
self.assertEqual(filter_group.filter, new_filter)
|
|
262
261
|
|
|
263
262
|
# Restore the old filter.
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
filter_group.filter = old_filter
|
|
264
|
+
filter_group.save()
|
|
266
265
|
|
|
267
266
|
def test_get_for_object(self):
|
|
268
267
|
"""Test `DynamicGroup.objects.get_for_object()`."""
|
|
@@ -272,12 +271,12 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
272
271
|
# Assert that the groups we got from `get_for_object()` match the lookup
|
|
273
272
|
# from the group instance itself.
|
|
274
273
|
device1_groups = DynamicGroup.objects.get_for_object(device1)
|
|
275
|
-
self.
|
|
274
|
+
self.assertQuerysetEqualAndNotEmpty(device1_groups, device1.dynamic_groups)
|
|
276
275
|
|
|
277
276
|
# Device4 should not be in ANY Dynamic Groups.
|
|
278
277
|
device4_groups = DynamicGroup.objects.get_for_object(device4)
|
|
279
278
|
self.assertEqual(list(device4_groups), [])
|
|
280
|
-
self.
|
|
279
|
+
self.assertQuerysetEqual(device4.dynamic_groups, [])
|
|
281
280
|
|
|
282
281
|
def test_members(self):
|
|
283
282
|
"""Test `DynamicGroup.members`."""
|
|
@@ -288,6 +287,50 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
288
287
|
self.assertIn(device1, group.members)
|
|
289
288
|
self.assertNotIn(device2, group.members)
|
|
290
289
|
|
|
290
|
+
def test_static_member_operations(self):
|
|
291
|
+
sg = DynamicGroup.objects.create(
|
|
292
|
+
name="All Prefixes",
|
|
293
|
+
content_type=ContentType.objects.get_for_model(Prefix),
|
|
294
|
+
group_type=DynamicGroupTypeChoices.TYPE_STATIC,
|
|
295
|
+
)
|
|
296
|
+
self.assertIsInstance(sg.members, PrefixQuerySet)
|
|
297
|
+
self.assertEqual(sg.members.count(), 0)
|
|
298
|
+
# test type validation
|
|
299
|
+
with self.assertRaises(TypeError):
|
|
300
|
+
sg.add_members([IPAddress.objects.first()])
|
|
301
|
+
# test bulk addition
|
|
302
|
+
sg.add_members(Prefix.objects.filter(ip_version=4))
|
|
303
|
+
self.assertIsInstance(sg.members, PrefixQuerySet)
|
|
304
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
|
|
305
|
+
# test duplicate objects aren't re-added
|
|
306
|
+
sg.add_members(Prefix.objects.all())
|
|
307
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
|
|
308
|
+
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
|
|
309
|
+
# test idempotence and alternate code path
|
|
310
|
+
sg.add_members(list(Prefix.objects.all()))
|
|
311
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
|
|
312
|
+
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
|
|
313
|
+
|
|
314
|
+
# test bulk removal
|
|
315
|
+
sg.remove_members(Prefix.objects.filter(ip_version=4))
|
|
316
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
317
|
+
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
|
|
318
|
+
# test idempotence and alternate code path
|
|
319
|
+
sg.remove_members(list(Prefix.objects.filter(ip_version=4)))
|
|
320
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
321
|
+
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
|
|
322
|
+
|
|
323
|
+
# test property setter
|
|
324
|
+
sg.members = Prefix.objects.filter(ip_version=4)
|
|
325
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
|
|
326
|
+
sg.members = list(Prefix.objects.filter(ip_version=6))
|
|
327
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
328
|
+
|
|
329
|
+
self.assertIsInstance(Prefix.objects.filter(ip_version=6).first().dynamic_groups, QuerySet)
|
|
330
|
+
self.assertIn(sg, list(Prefix.objects.filter(ip_version=6).first().dynamic_groups))
|
|
331
|
+
|
|
332
|
+
# TODO negative test that members=, add_members(), remove_members() raise appropriate errors for non-static groups
|
|
333
|
+
|
|
291
334
|
def test_members_fail_closed(self):
|
|
292
335
|
"""An invalid filter should fail closed, not fail open."""
|
|
293
336
|
self.assertFalse(self.invalid_filter.members.exists())
|
|
@@ -336,8 +379,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
336
379
|
expected,
|
|
337
380
|
)
|
|
338
381
|
|
|
339
|
-
# Now also test that an
|
|
340
|
-
# the same number of members.
|
|
382
|
+
# Now also test that an advanced (nested) dynamic group also reports the same number of members.
|
|
341
383
|
parent_group = DynamicGroup.objects.create(
|
|
342
384
|
name="Parent of Devices Location",
|
|
343
385
|
content_type=self.device_ct,
|
|
@@ -353,6 +395,32 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
353
395
|
expected,
|
|
354
396
|
)
|
|
355
397
|
|
|
398
|
+
def test_has_member(self):
|
|
399
|
+
"""Test `DynamicGroup.has_member()`."""
|
|
400
|
+
group = self.first_child
|
|
401
|
+
device1 = self.devices[0]
|
|
402
|
+
device2 = self.devices[1]
|
|
403
|
+
|
|
404
|
+
with self.assertApproximateNumQueries(minimum=1, maximum=10):
|
|
405
|
+
group.update_cached_members()
|
|
406
|
+
with self.assertNumQueries(1):
|
|
407
|
+
self.assertTrue(group.has_member(device1))
|
|
408
|
+
with self.assertNumQueries(1):
|
|
409
|
+
self.assertFalse(group.has_member(device2))
|
|
410
|
+
# Test idempotence
|
|
411
|
+
group.update_cached_members()
|
|
412
|
+
|
|
413
|
+
# Test fail-closed behavior of an invalid group filter
|
|
414
|
+
group = self.invalid_filter
|
|
415
|
+
with self.assertApproximateNumQueries(minimum=1, maximum=5):
|
|
416
|
+
group.update_cached_members()
|
|
417
|
+
with self.assertNumQueries(1):
|
|
418
|
+
self.assertFalse(group.has_member(device1))
|
|
419
|
+
with self.assertNumQueries(1):
|
|
420
|
+
self.assertFalse(group.has_member(device2))
|
|
421
|
+
# Test idempotence
|
|
422
|
+
group.update_cached_members()
|
|
423
|
+
|
|
356
424
|
def test_count(self):
|
|
357
425
|
"""Test `DynamicGroup.count`."""
|
|
358
426
|
expected = {
|
|
@@ -367,29 +435,6 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
367
435
|
for grp, cnt in expected.items():
|
|
368
436
|
self.assertEqual(grp, cnt)
|
|
369
437
|
|
|
370
|
-
def test_get_queryset(self):
|
|
371
|
-
"""Test `DynamicGroup.get_queryset()`."""
|
|
372
|
-
group = self.first_child
|
|
373
|
-
device1 = self.devices[0]
|
|
374
|
-
|
|
375
|
-
# Test that we can get a full queryset
|
|
376
|
-
qs = group.get_queryset()
|
|
377
|
-
devices = group.model.objects.filter(location=device1.location)
|
|
378
|
-
|
|
379
|
-
# Expect a single-member qs/list of Device names (only `device1`)
|
|
380
|
-
expected = [device1.name]
|
|
381
|
-
self.assertIn(device1, devices)
|
|
382
|
-
self.assertIn(device1, qs)
|
|
383
|
-
self.assertEqual(list(map(str, devices)), expected)
|
|
384
|
-
self.assertEqual(list(map(str, qs)), expected)
|
|
385
|
-
self.assertEqual(list(qs), list(devices))
|
|
386
|
-
|
|
387
|
-
# A new group that doesn't have a content_type and therefore
|
|
388
|
-
# `self.model`, should raise a RuntimeError
|
|
389
|
-
new_group = DynamicGroup()
|
|
390
|
-
with self.assertRaises(RuntimeError):
|
|
391
|
-
new_group.get_queryset()
|
|
392
|
-
|
|
393
438
|
def test_model(self):
|
|
394
439
|
"""Test `DynamicGroup.model`."""
|
|
395
440
|
# New instances should not have a model unless `content_type` is set.
|
|
@@ -400,19 +445,18 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
400
445
|
new_group.content_type = self.device_ct
|
|
401
446
|
self.assertIsNotNone(new_group.model)
|
|
402
447
|
|
|
403
|
-
def
|
|
404
|
-
"""Test `DynamicGroup.
|
|
448
|
+
def test_object_classes(self):
|
|
449
|
+
"""Test `DynamicGroup object_class dynamic population."""
|
|
405
450
|
# New instances should fail to map until `content_type` is set.
|
|
406
451
|
new_group = DynamicGroup(name="Unsaved Group")
|
|
407
|
-
|
|
408
|
-
self.
|
|
452
|
+
self.assertIsNone(new_group.filterset_class)
|
|
453
|
+
self.assertIsNone(new_group.filterform_class)
|
|
454
|
+
self.assertIsNone(new_group.form_class)
|
|
409
455
|
|
|
410
456
|
# Existing groups w/ `content_type` set work as expected.
|
|
411
457
|
group = self.groups[0]
|
|
412
458
|
model = group.content_type.model_class()
|
|
413
|
-
objects_mapped = group._set_object_classes(model)
|
|
414
459
|
|
|
415
|
-
self.assertTrue(objects_mapped)
|
|
416
460
|
self.assertEqual(group.model, model)
|
|
417
461
|
self.assertEqual(group.filterset_class, DeviceFilterSet)
|
|
418
462
|
self.assertEqual(group.filterform_class, DeviceFilterForm)
|
|
@@ -573,7 +617,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
573
617
|
|
|
574
618
|
def test_set_filter(self):
|
|
575
619
|
"""Test `DynamicGroup.set_filter()`."""
|
|
576
|
-
group = self.
|
|
620
|
+
group = self.first_child
|
|
577
621
|
|
|
578
622
|
# Input can come from a form's cleaned_data, such as our generated form. In this case, the
|
|
579
623
|
# filter we set from the form should be identical to what was there already.
|
|
@@ -611,6 +655,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
611
655
|
"""Test various ways in which adding a child group should fail."""
|
|
612
656
|
parent = self.parent
|
|
613
657
|
parent.filter = {"location": ["Location 1"]}
|
|
658
|
+
parent.group_type = DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER
|
|
614
659
|
child = self.no_match_filter
|
|
615
660
|
|
|
616
661
|
# parent.add_child() should fail
|
|
@@ -634,36 +679,36 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
634
679
|
self.assertFalse(self.parent.children.filter(name=self.third_child.name).exists())
|
|
635
680
|
|
|
636
681
|
def test_generate_query_for_filter(self):
|
|
637
|
-
"""Test `DynamicGroup.
|
|
638
|
-
group = self.
|
|
682
|
+
"""Test `DynamicGroup._generate_query_for_filter()`."""
|
|
683
|
+
group = self.first_child # Any filter-based group will do, so why not this one?
|
|
639
684
|
multi_value = ["Location 3"]
|
|
640
685
|
fs = group.filterset_class()
|
|
641
686
|
multi_field = fs.filters["location"]
|
|
642
|
-
multi_query = group.
|
|
687
|
+
multi_query = group._generate_query_for_filter(
|
|
643
688
|
filter_field=multi_field,
|
|
644
689
|
value=multi_value,
|
|
645
690
|
)
|
|
646
691
|
|
|
647
|
-
queryset = group.
|
|
692
|
+
queryset = group.model.objects.all()
|
|
648
693
|
|
|
649
694
|
# Assert that both querysets return the same results
|
|
650
695
|
group_qs = queryset.filter(multi_query)
|
|
651
696
|
device_qs = Device.objects.filter(location__name__in=multi_value)
|
|
652
|
-
self.
|
|
697
|
+
self.assertQuerysetEqual(group_qs, device_qs, ordered=False)
|
|
653
698
|
|
|
654
699
|
# Now do a non-multi-value filter.
|
|
655
700
|
solo_field = fs.filters["has_interfaces"]
|
|
656
701
|
solo_value = False
|
|
657
|
-
solo_query = group.
|
|
702
|
+
solo_query = group._generate_query_for_filter(filter_field=solo_field, value=solo_value)
|
|
658
703
|
solo_qs = queryset.filter(solo_query)
|
|
659
704
|
interface_qs = Device.objects.filter(interfaces__isnull=True)
|
|
660
|
-
self.
|
|
705
|
+
self.assertQuerysetEqual(solo_qs, interface_qs, ordered=False)
|
|
661
706
|
|
|
662
707
|
# Tags are conjoined in the TagFilterSet, ensure that tags__name is using AND. We know this isn't right
|
|
663
708
|
# since the resulting query actually does tag.name == tag_1 AND tag.name == tag_2, but django_filter does
|
|
664
709
|
# not use Q evaluation for conjoined filters. This function is only used for the display, and the display
|
|
665
710
|
# is good enough to get the point across.
|
|
666
|
-
tags_query = group.
|
|
711
|
+
tags_query = group._generate_query_for_filter(filter_field=fs.filters["tags"], value=["tag_1", "tag_2"])
|
|
667
712
|
self.assertEqual(str(tags_query), "(AND: ('tags__name', 'tag_1'), ('tags__name', 'tag_2'))")
|
|
668
713
|
|
|
669
714
|
# Test that a nested field_name w/ `generate_query` works as expected. This is explicitly to
|
|
@@ -677,39 +722,32 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
677
722
|
# We are making sure the filterset generated from the name as an argument results in the same
|
|
678
723
|
# filtered queryset, and more importantly that the nested filter expression `device_type__manufacturer`
|
|
679
724
|
# is automatically used to get the related model name without failing.
|
|
680
|
-
nested_query = group.
|
|
725
|
+
nested_query = group._generate_query_for_filter(filter_field=fs.filters["manufacturer"], value=nested_value)
|
|
681
726
|
nested_qs = queryset.filter(nested_query)
|
|
682
727
|
parent_qs = Device.objects.filter(device_type__manufacturer__name__in=nested_value)
|
|
683
|
-
self.
|
|
728
|
+
self.assertQuerysetEqual(nested_qs, parent_qs, ordered=False)
|
|
684
729
|
|
|
685
|
-
def
|
|
686
|
-
"""Test `DynamicGroup.
|
|
687
|
-
group
|
|
688
|
-
|
|
689
|
-
# A group with an empty filter will have a null `Q` object
|
|
690
|
-
parent_q = group.generate_query_for_group(group)
|
|
691
|
-
self.assertFalse(parent_q)
|
|
692
|
-
|
|
693
|
-
# A child group with a filter set will result in a useful Q object.
|
|
694
|
-
child_q = group.generate_query_for_group(self.second_child)
|
|
730
|
+
def test_generate_filter_based_query(self):
|
|
731
|
+
"""Test `DynamicGroup._generate_filter_based_query()`."""
|
|
732
|
+
# A group with a filter set will result in a useful Q object.
|
|
733
|
+
child_q = self.second_child._generate_filter_based_query()
|
|
695
734
|
lookup_kwargs = dict(child_q.children) # {name: value}
|
|
696
735
|
|
|
697
|
-
# Assert that both querysets
|
|
698
|
-
group_qs =
|
|
736
|
+
# Assert that both querysets return the same results
|
|
737
|
+
group_qs = self.second_child.members
|
|
699
738
|
device_qs = Device.objects.filter(**lookup_kwargs)
|
|
700
|
-
self.
|
|
739
|
+
self.assertQuerysetEqual(group_qs, device_qs, ordered=False)
|
|
701
740
|
|
|
702
741
|
def test_get_group_queryset(self):
|
|
703
|
-
"""Test `DynamicGroup.
|
|
704
|
-
# This is literally just calling `process_group_filters(self)` so let's
|
|
705
|
-
# just make sure that it stays consistent until we decide otherwise.
|
|
742
|
+
"""Test `DynamicGroup._get_group_queryset()`."""
|
|
706
743
|
group = self.parent
|
|
707
|
-
group_qs = group.
|
|
744
|
+
group_qs = group._get_group_queryset()
|
|
745
|
+
|
|
708
746
|
process_query = group.generate_query()
|
|
709
|
-
base_qs =
|
|
747
|
+
base_qs = Device.objects.all()
|
|
710
748
|
process_qs = base_qs.filter(process_query)
|
|
711
749
|
|
|
712
|
-
self.
|
|
750
|
+
self.assertQuerysetEqual(group_qs, process_qs, ordered=False)
|
|
713
751
|
|
|
714
752
|
def test_get_ancestors(self):
|
|
715
753
|
"""Test `DynamicGroup.get_ancestors()`."""
|
|
@@ -824,7 +862,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
824
862
|
# Start with parent. Enumerate descendants and their operators to assert correct results.
|
|
825
863
|
group = self.parent
|
|
826
864
|
group_query = group.generate_query()
|
|
827
|
-
group_qs =
|
|
865
|
+
group_qs = Device.objects.filter(group_query)
|
|
828
866
|
|
|
829
867
|
# <DynamicGroupMembership: First Child: intersection (10)>
|
|
830
868
|
# <DynamicGroupMembership: Second Child: union (20)>
|
|
@@ -834,7 +872,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
834
872
|
# Manually iterate over the groups to assert the same result that the queryset should have,
|
|
835
873
|
# igoring weight since each set of members are already ordered by weight when queried from
|
|
836
874
|
# the database.
|
|
837
|
-
child_set = set(
|
|
875
|
+
child_set = set(Device.objects.all())
|
|
838
876
|
for member in group_members:
|
|
839
877
|
child_members = set(member.members)
|
|
840
878
|
operator = member.operator
|
|
@@ -870,6 +908,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
870
908
|
|
|
871
909
|
device = self.devices[0]
|
|
872
910
|
prefix = Prefix.objects.first()
|
|
911
|
+
self.assertIsNotNone(prefix)
|
|
873
912
|
|
|
874
913
|
relationship = Relationship(
|
|
875
914
|
label="Device to Prefix",
|
|
@@ -984,7 +1023,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
984
1023
|
"""
|
|
985
1024
|
Test that tags without being applied to any member instances can still be added as filters on DynamicGroups
|
|
986
1025
|
"""
|
|
987
|
-
dg = self.
|
|
1026
|
+
dg = self.first_child
|
|
988
1027
|
unapplied_tag = Tag.objects.create(name="Unapplied Tag")
|
|
989
1028
|
unapplied_tag.content_types.set([ContentType.objects.get_for_model(Device)])
|
|
990
1029
|
unapplied_tag.save()
|
|
@@ -994,58 +1033,10 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
994
1033
|
def test_member_caching_output(self):
|
|
995
1034
|
group = self.first_child
|
|
996
1035
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1036
|
+
updated_members = group.update_cached_members()
|
|
1037
|
+
self.assertEqual(sorted(list(group.members)), sorted(list(updated_members)))
|
|
1000
1038
|
self.assertEqual(sorted(list(group.members)), sorted(list(group.members_cached)))
|
|
1001
1039
|
|
|
1002
|
-
@override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=2)
|
|
1003
|
-
def test_member_caching_enabled(self):
|
|
1004
|
-
"""
|
|
1005
|
-
Verify that the members list of the DynamicGroup is cached and expires.
|
|
1006
|
-
"""
|
|
1007
|
-
group = self.first_child
|
|
1008
|
-
|
|
1009
|
-
class FakeQuerySet:
|
|
1010
|
-
def all(self):
|
|
1011
|
-
return []
|
|
1012
|
-
|
|
1013
|
-
# Ensure the cache is empty from previous tests
|
|
1014
|
-
cache.delete(group.members_cache_key)
|
|
1015
|
-
|
|
1016
|
-
with patch.object(group, "get_queryset", return_value=FakeQuerySet()) as mock_get_queryset:
|
|
1017
|
-
group.members_cached
|
|
1018
|
-
group.members_cached
|
|
1019
|
-
group.members_cached
|
|
1020
|
-
self.assertEqual(mock_get_queryset.call_count, 1)
|
|
1021
|
-
|
|
1022
|
-
time.sleep(5) # Let the cache expire
|
|
1023
|
-
|
|
1024
|
-
group.members_cached
|
|
1025
|
-
self.assertEqual(mock_get_queryset.call_count, 2)
|
|
1026
|
-
|
|
1027
|
-
# Clean-up after ourselves
|
|
1028
|
-
cache.delete(group.members_cache_key)
|
|
1029
|
-
|
|
1030
|
-
@override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=0)
|
|
1031
|
-
def test_member_caching_disabled(self):
|
|
1032
|
-
"""
|
|
1033
|
-
Verify that the members list of the DynamicGroup is not cached.
|
|
1034
|
-
"""
|
|
1035
|
-
group = self.first_child
|
|
1036
|
-
|
|
1037
|
-
class FakeQuerySet:
|
|
1038
|
-
def all(self):
|
|
1039
|
-
return []
|
|
1040
|
-
|
|
1041
|
-
# Ensure the cache is empty from previous tests
|
|
1042
|
-
cache.delete(group.members_cache_key)
|
|
1043
|
-
|
|
1044
|
-
with patch.object(group, "get_queryset", return_value=FakeQuerySet()) as mock_get_queryset:
|
|
1045
|
-
group.members_cached
|
|
1046
|
-
group.members_cached
|
|
1047
|
-
self.assertEqual(mock_get_queryset.call_count, 2)
|
|
1048
|
-
|
|
1049
1040
|
|
|
1050
1041
|
class DynamicGroupMembershipModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mixin?
|
|
1051
1042
|
"""DynamicGroupMembership model tests."""
|
|
@@ -1112,60 +1103,99 @@ class DynamicGroupMembershipModelTest(DynamicGroupTestBase): # TODO: BaseModelT
|
|
|
1112
1103
|
self.assertEqual(mem.get_group_members_url(), grp.get_group_members_url())
|
|
1113
1104
|
|
|
1114
1105
|
|
|
1115
|
-
class
|
|
1106
|
+
class DynamicGroupMixinModelTest(DynamicGroupTestBase):
|
|
1107
|
+
"""DynamicGroupMixin model tests."""
|
|
1108
|
+
|
|
1109
|
+
def test_dynamic_groups(self):
|
|
1110
|
+
with self.assertApproximateNumQueries(minimum=len(self.groups), maximum=10 * len(self.groups)):
|
|
1111
|
+
for group in self.groups:
|
|
1112
|
+
group.update_cached_members()
|
|
1113
|
+
with self.assertNumQueries(1):
|
|
1114
|
+
qs = self.devices[0].dynamic_groups
|
|
1115
|
+
list(qs)
|
|
1116
|
+
self.assertQuerysetEqualAndNotEmpty(qs, [self.first_child, self.third_child, self.nested_child], ordered=False)
|
|
1117
|
+
|
|
1118
|
+
def test_dynamic_groups_cached(self):
|
|
1119
|
+
for group in self.groups:
|
|
1120
|
+
group.update_cached_members()
|
|
1121
|
+
with self.assertNumQueries(1):
|
|
1122
|
+
qs = self.devices[0].dynamic_groups_cached
|
|
1123
|
+
list(qs)
|
|
1124
|
+
self.assertQuerysetEqualAndNotEmpty(qs, [self.first_child, self.third_child, self.nested_child], ordered=False)
|
|
1125
|
+
|
|
1126
|
+
def test_dynamic_groups_list(self):
|
|
1127
|
+
for group in self.groups:
|
|
1128
|
+
group.update_cached_members()
|
|
1129
|
+
with self.assertNumQueries(1):
|
|
1130
|
+
groups = self.devices[0].dynamic_groups_list
|
|
1131
|
+
self.assertEqual(set(groups), set([self.first_child, self.third_child, self.nested_child]))
|
|
1132
|
+
|
|
1133
|
+
def test_dynamic_groups_list_cached(self):
|
|
1134
|
+
for group in self.groups:
|
|
1135
|
+
group.update_cached_members()
|
|
1136
|
+
with self.assertNumQueries(1):
|
|
1137
|
+
groups = self.devices[0].dynamic_groups_list_cached
|
|
1138
|
+
self.assertEqual(set(groups), set([self.first_child, self.third_child, self.nested_child]))
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
class DynamicGroupFilterTest(DynamicGroupTestBase, FilterTestCases.FilterTestCase):
|
|
1116
1142
|
"""DynamicGroup instance filterset tests."""
|
|
1117
1143
|
|
|
1118
1144
|
queryset = DynamicGroup.objects.all()
|
|
1119
1145
|
filterset = DynamicGroupFilterSet
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
params = {"name": ["First Child", "Third Child"]}
|
|
1127
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1146
|
+
generic_filter_tests = (
|
|
1147
|
+
["name"],
|
|
1148
|
+
["description"],
|
|
1149
|
+
["group_type"],
|
|
1150
|
+
["tenant", "tenant__name"],
|
|
1151
|
+
)
|
|
1128
1152
|
|
|
1129
1153
|
def test_content_type(self):
|
|
1130
1154
|
params = {"content_type": ["dcim.device", "virtualization.virtualmachine"]}
|
|
1131
|
-
self.
|
|
1155
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1156
|
+
self.filterset(params, self.queryset).qs,
|
|
1157
|
+
DynamicGroup.objects.filter(
|
|
1158
|
+
content_type__in=[
|
|
1159
|
+
ContentType.objects.get_for_model(Device),
|
|
1160
|
+
ContentType.objects.get_for_model(VirtualMachine),
|
|
1161
|
+
]
|
|
1162
|
+
),
|
|
1163
|
+
)
|
|
1132
1164
|
|
|
1133
1165
|
def test_search(self):
|
|
1134
1166
|
tests = {
|
|
1135
1167
|
"Devices No Filter": 0, # name
|
|
1136
1168
|
"Invalid Filter": 1, # name
|
|
1137
1169
|
"A group with a non-matching filter": 1, # description
|
|
1138
|
-
"dcim": 8, # content_type__app_label
|
|
1139
|
-
"device": 8, # content_type__model
|
|
1140
1170
|
}
|
|
1141
1171
|
for value, cnt in tests.items():
|
|
1142
1172
|
params = {"q": value}
|
|
1143
1173
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), cnt)
|
|
1144
1174
|
|
|
1175
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1176
|
+
self.filterset({"q": "dcim"}).qs,
|
|
1177
|
+
DynamicGroup.objects.filter(content_type__app_label="dcim"),
|
|
1178
|
+
)
|
|
1179
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
1180
|
+
self.filterset({"q": "device"}).qs,
|
|
1181
|
+
DynamicGroup.objects.filter(content_type__model__icontains="device"),
|
|
1182
|
+
)
|
|
1145
1183
|
|
|
1146
|
-
|
|
1184
|
+
|
|
1185
|
+
class DynamicGroupMembershipFilterTest(DynamicGroupTestBase, FilterTestCases.FilterTestCase):
|
|
1147
1186
|
"""DynamicGroupMembership instance filterset tests."""
|
|
1148
1187
|
|
|
1149
1188
|
queryset = DynamicGroupMembership.objects.all()
|
|
1150
1189
|
filterset = DynamicGroupMembershipFilterSet
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
def test_weight(self):
|
|
1161
|
-
params = {"weight": [10]}
|
|
1162
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1163
|
-
|
|
1164
|
-
def test_group(self):
|
|
1165
|
-
group_pk = self.queryset.first().group.pk # expecting 1
|
|
1166
|
-
group_name = self.queryset.last().group.name # expecting 1
|
|
1167
|
-
params = {"group": [group_pk, group_name]}
|
|
1168
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
1190
|
+
generic_filter_tests = (
|
|
1191
|
+
["operator"],
|
|
1192
|
+
["weight"],
|
|
1193
|
+
["group", "group__id"],
|
|
1194
|
+
["group", "group__name"],
|
|
1195
|
+
# ["parent_group", "parent_group__id"], # would work but we only have 2 valid parent groups
|
|
1196
|
+
# ["parent_group", "parent_group__name"], # would work but we only have 2 valid parent groups
|
|
1197
|
+
)
|
|
1198
|
+
exclude_q_filter_predicates = ["operator"]
|
|
1169
1199
|
|
|
1170
1200
|
def test_parent_group(self):
|
|
1171
1201
|
parent_group_pk = self.queryset.first().parent_group.pk # expecting 3
|