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/core/testing/views.py
CHANGED
|
@@ -269,7 +269,7 @@ class ViewTestCases:
|
|
|
269
269
|
response = self.client.get(url)
|
|
270
270
|
self.assertHttpStatus(response, 200)
|
|
271
271
|
response_data = response.content.decode(response.charset)
|
|
272
|
-
if
|
|
272
|
+
if getattr(obj, "is_contact_associable_model", False):
|
|
273
273
|
self.assertInHTML(
|
|
274
274
|
f'<a href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)" aria-controls="contacts" role="tab" data-toggle="tab">Contacts</a>',
|
|
275
275
|
response_data,
|
|
@@ -288,7 +288,7 @@ class ViewTestCases:
|
|
|
288
288
|
response = self.client.get(url)
|
|
289
289
|
self.assertHttpStatus(response, 200)
|
|
290
290
|
response_data = response.content.decode(response.charset)
|
|
291
|
-
if
|
|
291
|
+
if getattr(obj, "is_contact_associable_model", False):
|
|
292
292
|
self.assertInHTML(
|
|
293
293
|
f'<a href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)" aria-controls="contacts" role="tab" data-toggle="tab">Contacts</a>',
|
|
294
294
|
response_data,
|
|
@@ -467,10 +467,12 @@ class ViewTestCases:
|
|
|
467
467
|
"""
|
|
468
468
|
Edit a single existing instance.
|
|
469
469
|
|
|
470
|
-
:
|
|
470
|
+
:update_data: Data to be used when updating the first existing object, fall back to form_data if not provided.
|
|
471
|
+
:form_data: Fall back to this data if update_data is not provided, for backward compatibility.
|
|
471
472
|
"""
|
|
472
473
|
|
|
473
474
|
form_data = {}
|
|
475
|
+
update_data = {}
|
|
474
476
|
|
|
475
477
|
def test_edit_object_without_permission(self):
|
|
476
478
|
instance = self._get_queryset().first()
|
|
@@ -480,9 +482,10 @@ class ViewTestCases:
|
|
|
480
482
|
self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), [403, 404])
|
|
481
483
|
|
|
482
484
|
# Try POST without permission
|
|
485
|
+
update_data = self.update_data or self.form_data
|
|
483
486
|
request = {
|
|
484
487
|
"path": self._get_url("edit", instance),
|
|
485
|
-
"data": utils.post_data(
|
|
488
|
+
"data": utils.post_data(update_data),
|
|
486
489
|
}
|
|
487
490
|
with utils.disable_warnings("django.request"):
|
|
488
491
|
self.assertHttpStatus(self.client.post(**request), [403, 404])
|
|
@@ -501,17 +504,17 @@ class ViewTestCases:
|
|
|
501
504
|
self.assertHttpStatus(self.client.get(self._get_url("edit", instance)), 200)
|
|
502
505
|
|
|
503
506
|
# Try POST with model-level permission
|
|
507
|
+
update_data = self.update_data or self.form_data
|
|
504
508
|
request = {
|
|
505
509
|
"path": self._get_url("edit", instance),
|
|
506
|
-
"data": utils.post_data(
|
|
510
|
+
"data": utils.post_data(update_data),
|
|
507
511
|
}
|
|
508
512
|
self.assertHttpStatus(self.client.post(**request), 302)
|
|
509
|
-
self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk),
|
|
513
|
+
self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), update_data)
|
|
510
514
|
|
|
511
515
|
if hasattr(self.model, "to_objectchange"):
|
|
512
516
|
# Verify ObjectChange creation
|
|
513
517
|
objectchanges = lookup.get_changes_for_model(instance)
|
|
514
|
-
self.assertEqual(len(objectchanges), 1)
|
|
515
518
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
|
|
516
519
|
# Validate if detail view exists
|
|
517
520
|
validate = URLValidator()
|
|
@@ -549,17 +552,18 @@ class ViewTestCases:
|
|
|
549
552
|
self.assertHttpStatus(self.client.get(self._get_url("edit", instance2)), 404)
|
|
550
553
|
|
|
551
554
|
# Try to edit a permitted object
|
|
555
|
+
update_data = self.update_data or self.form_data
|
|
552
556
|
request = {
|
|
553
557
|
"path": self._get_url("edit", instance1),
|
|
554
|
-
"data": utils.post_data(
|
|
558
|
+
"data": utils.post_data(update_data),
|
|
555
559
|
}
|
|
556
560
|
self.assertHttpStatus(self.client.post(**request), 302)
|
|
557
|
-
self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk),
|
|
561
|
+
self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk), update_data)
|
|
558
562
|
|
|
559
563
|
# Try to edit a non-permitted object
|
|
560
564
|
request = {
|
|
561
565
|
"path": self._get_url("edit", instance2),
|
|
562
|
-
"data": utils.post_data(
|
|
566
|
+
"data": utils.post_data(update_data),
|
|
563
567
|
}
|
|
564
568
|
self.assertHttpStatus(self.client.post(**request), 404)
|
|
565
569
|
|
|
@@ -637,7 +641,6 @@ class ViewTestCases:
|
|
|
637
641
|
if hasattr(self.model, "to_objectchange"):
|
|
638
642
|
# Verify ObjectChange creation
|
|
639
643
|
objectchanges = lookup.get_changes_for_model(instance)
|
|
640
|
-
self.assertEqual(len(objectchanges), 1)
|
|
641
644
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_DELETE)
|
|
642
645
|
|
|
643
646
|
if hasattr(self.model, "notes") and isinstance(instance.notes, extras_querysets.NotesQuerySet):
|
|
@@ -678,7 +681,6 @@ class ViewTestCases:
|
|
|
678
681
|
if hasattr(self.model, "to_objectchange"):
|
|
679
682
|
# Verify ObjectChange creation
|
|
680
683
|
objectchanges = lookup.get_changes_for_model(instance)
|
|
681
|
-
self.assertEqual(len(objectchanges), 1)
|
|
682
684
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_DELETE)
|
|
683
685
|
|
|
684
686
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
@@ -790,6 +792,10 @@ class ViewTestCases:
|
|
|
790
792
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
791
793
|
def test_list_objects_filtered(self):
|
|
792
794
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
795
|
+
if hasattr(self.model, "name") and instance1.name == instance2.name:
|
|
796
|
+
instance2.name += "X"
|
|
797
|
+
instance2.save()
|
|
798
|
+
|
|
793
799
|
response = self.client.get(f"{self._get_url('list')}?id={instance1.pk}")
|
|
794
800
|
self.assertHttpStatus(response, 200)
|
|
795
801
|
content = utils.extract_page_body(response.content.decode(response.charset))
|
|
@@ -815,6 +821,10 @@ class ViewTestCases:
|
|
|
815
821
|
def test_list_objects_unknown_filter_no_strict_filtering(self):
|
|
816
822
|
"""Verify that without STRICT_FILTERING, an unknown filter is ignored."""
|
|
817
823
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
824
|
+
if hasattr(self.model, "name") and instance1.name == instance2.name:
|
|
825
|
+
instance2.name += "X"
|
|
826
|
+
instance2.save()
|
|
827
|
+
|
|
818
828
|
with self.assertLogs("nautobot.core.filters") as cm:
|
|
819
829
|
response = self.client.get(f"{self._get_url('list')}?ice_cream_flavor=chocolate")
|
|
820
830
|
filterset = self.get_filterset()
|
|
@@ -881,6 +891,9 @@ class ViewTestCases:
|
|
|
881
891
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
882
892
|
def test_list_objects_with_constrained_permission(self):
|
|
883
893
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
894
|
+
if hasattr(self.model, "name") and instance1.name == instance2.name:
|
|
895
|
+
instance2.name += "X"
|
|
896
|
+
instance2.save()
|
|
884
897
|
|
|
885
898
|
# Add object-level permission
|
|
886
899
|
obj_perm = users_models.ObjectPermission(
|
|
@@ -1409,7 +1422,9 @@ class ViewTestCases:
|
|
|
1409
1422
|
# Try POST with model-level permission
|
|
1410
1423
|
self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
|
|
1411
1424
|
for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
|
|
1412
|
-
|
|
1425
|
+
name = getattr(instance, "name")
|
|
1426
|
+
expected_name = getattr(objects[i], "name") + "X"
|
|
1427
|
+
self.assertEqual(name, expected_name)
|
|
1413
1428
|
|
|
1414
1429
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1415
1430
|
def test_bulk_rename_objects_with_constrained_permission(self):
|
|
@@ -1442,7 +1457,9 @@ class ViewTestCases:
|
|
|
1442
1457
|
# Bulk rename permitted objects
|
|
1443
1458
|
self.assertHttpStatus(self.client.post(self._get_url("bulk_rename"), data), 302)
|
|
1444
1459
|
for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
|
|
1445
|
-
|
|
1460
|
+
name = getattr(instance, "name")
|
|
1461
|
+
expected_name = getattr(objects[i], "name") + "X"
|
|
1462
|
+
self.assertEqual(name, expected_name)
|
|
1446
1463
|
|
|
1447
1464
|
class PrimaryObjectViewTestCase(
|
|
1448
1465
|
GetObjectViewTestCase,
|
|
@@ -18,6 +18,7 @@ class ImportObjectsUITestCase(SeleniumTestCase):
|
|
|
18
18
|
self.browser.visit(self.live_server_url)
|
|
19
19
|
self.browser.links.find_by_partial_text("Organization").click()
|
|
20
20
|
self.browser.links.find_by_partial_text("Locations").click()
|
|
21
|
+
self.browser.find_by_id("actions-dropdown").click()
|
|
21
22
|
self.browser.find_by_id("import-button").click()
|
|
22
23
|
|
|
23
24
|
# Make sure the table of fields for a Location import is populated via a few spot checks
|
|
@@ -20,7 +20,7 @@ class SwaggerUITestCase(SeleniumTestCase):
|
|
|
20
20
|
"""Check that the dcim.location API endpoints are rendered correctly."""
|
|
21
21
|
self.browser.visit(self.live_server_url + reverse("api_docs"))
|
|
22
22
|
# Wait for Swagger UI to load, look for the location-list endpoint in the UI and click on it to expand it.
|
|
23
|
-
dcim_locations_list = self.browser.find_by_id("operations-dcim-dcim_locations_list", wait_time=
|
|
23
|
+
dcim_locations_list = self.browser.find_by_id("operations-dcim-dcim_locations_list", wait_time=60).first
|
|
24
24
|
dcim_locations_list.find_by_tag("button").first.click()
|
|
25
25
|
|
|
26
26
|
# Look for the "Try it out" button and click it
|
nautobot/core/tests/runner.py
CHANGED
|
@@ -13,9 +13,9 @@ from nautobot.core.celery import app, setup_nautobot_job_logging
|
|
|
13
13
|
from nautobot.core.settings_funcs import parse_redis_connection
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def init_worker_with_unique_cache(
|
|
16
|
+
def init_worker_with_unique_cache(*args, **kwargs):
|
|
17
17
|
"""Extend Django's default parallel unit test setup to also ensure distinct Redis caches."""
|
|
18
|
-
_init_worker(
|
|
18
|
+
_init_worker(*args, **kwargs) # call Django default to set _worker_id and set up parallel DB instances
|
|
19
19
|
# _worker_id is now 1, 2, 3, 4, etc.
|
|
20
20
|
|
|
21
21
|
from django.test.runner import _worker_id
|
nautobot/core/tests/test_api.py
CHANGED
|
@@ -4,6 +4,7 @@ from django.conf import settings
|
|
|
4
4
|
from django.contrib.auth import get_user_model
|
|
5
5
|
from django.contrib.auth.models import Group
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
|
+
from django.db.models import Q
|
|
7
8
|
from django.test.utils import override_settings
|
|
8
9
|
from django.urls import reverse
|
|
9
10
|
from netaddr import IPNetwork
|
|
@@ -573,10 +574,14 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
573
574
|
|
|
574
575
|
# Check against 1st user's response
|
|
575
576
|
self.assertEqual(response_user1.status_code, 200)
|
|
576
|
-
self.assertEqual(
|
|
577
|
+
self.assertEqual(
|
|
578
|
+
response_user1.data["count"], ObjectChange.objects.filter(Q(user=self.user) | Q(action="delete")).count()
|
|
579
|
+
)
|
|
577
580
|
self.assertEqual(response_user1.data["results"][0]["user"]["id"], self.user.pk)
|
|
578
581
|
|
|
579
582
|
# Check against 2nd user's response
|
|
580
583
|
self.assertEqual(response_user2.status_code, 200)
|
|
581
|
-
self.assertEqual(
|
|
584
|
+
self.assertEqual(
|
|
585
|
+
response_user2.data["count"], ObjectChange.objects.filter(Q(user=obj_user2) | Q(action="delete")).count()
|
|
586
|
+
)
|
|
582
587
|
self.assertEqual(response_user2.data["results"][0]["user"]["id"], obj_user2.pk)
|
|
@@ -142,6 +142,7 @@ class TreeNodeMultipleChoiceFilterTest(TestCase):
|
|
|
142
142
|
self.child_location_2ab,
|
|
143
143
|
self.child_location_same_name_2,
|
|
144
144
|
],
|
|
145
|
+
ordered=False,
|
|
145
146
|
)
|
|
146
147
|
|
|
147
148
|
def test_filter_null(self):
|
|
@@ -212,6 +213,7 @@ class TreeNodeMultipleChoiceFilterTest(TestCase):
|
|
|
212
213
|
self.child_location_2ab,
|
|
213
214
|
self.child_location_same_name_2,
|
|
214
215
|
],
|
|
216
|
+
ordered=False,
|
|
215
217
|
)
|
|
216
218
|
|
|
217
219
|
def test_filter_combined_name_exclude(self):
|
|
@@ -1304,14 +1306,14 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
|
|
|
1304
1306
|
parent=self.parent_location_1,
|
|
1305
1307
|
name="Test Child Location 1",
|
|
1306
1308
|
location_type=self.lt,
|
|
1307
|
-
asn=
|
|
1309
|
+
asn=12345,
|
|
1308
1310
|
status=status,
|
|
1309
1311
|
)
|
|
1310
1312
|
self.child_location_2 = dcim_models.Location.objects.create(
|
|
1311
1313
|
parent=self.parent_location_2,
|
|
1312
1314
|
name="Test Child Location 2",
|
|
1313
1315
|
location_type=self.lt,
|
|
1314
|
-
asn=
|
|
1316
|
+
asn=123456,
|
|
1315
1317
|
status=status,
|
|
1316
1318
|
)
|
|
1317
1319
|
self.child_location_3 = dcim_models.Location.objects.create(
|
|
@@ -1353,19 +1355,19 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
|
|
|
1353
1355
|
|
|
1354
1356
|
def test_default_exact(self):
|
|
1355
1357
|
"""Test a default search for an "exact" value."""
|
|
1356
|
-
params = {"q": "
|
|
1358
|
+
params = {"q": "12345"}
|
|
1357
1359
|
self.assertQuerysetEqualAndNotEmpty(
|
|
1358
|
-
self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=
|
|
1360
|
+
self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=12345)
|
|
1359
1361
|
)
|
|
1360
1362
|
asn = (
|
|
1361
|
-
dcim_models.Location.objects.exclude(asn="
|
|
1363
|
+
dcim_models.Location.objects.exclude(asn="12345")
|
|
1362
1364
|
.exclude(asn__isnull=True)
|
|
1363
1365
|
.values_list("asn", flat=True)
|
|
1364
1366
|
.first()
|
|
1365
1367
|
)
|
|
1366
1368
|
params = {"q": str(asn)}
|
|
1367
1369
|
self.assertQuerysetEqualAndNotEmpty(
|
|
1368
|
-
self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=
|
|
1370
|
+
self.filterset_class(params, self.queryset).qs, self.queryset.filter(asn__exact=asn)
|
|
1369
1371
|
)
|
|
1370
1372
|
|
|
1371
1373
|
def test_default_id(self):
|
|
@@ -1389,9 +1391,9 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
|
|
|
1389
1391
|
|
|
1390
1392
|
q = filters.SearchFilter(filter_predicates={"asn": {"lookup_expr": "exact", "preprocessor": int}})
|
|
1391
1393
|
|
|
1392
|
-
params = {"q": "
|
|
1394
|
+
params = {"q": "12345"}
|
|
1393
1395
|
self.assertQuerysetEqualAndNotEmpty(
|
|
1394
|
-
MyLocationFilterSet(params, self.queryset).qs, self.queryset.filter(asn__exact="
|
|
1396
|
+
MyLocationFilterSet(params, self.queryset).qs, self.queryset.filter(asn__exact="12345")
|
|
1395
1397
|
)
|
|
1396
1398
|
params = {"q": "123"}
|
|
1397
1399
|
# Both querysets are empty so we dont use assertQuerysetEqualAndNotEmpty here.
|
|
@@ -1403,7 +1405,7 @@ class SearchFilterTest(TestCase, testing.NautobotTestCaseMixin):
|
|
|
1403
1405
|
|
|
1404
1406
|
q = filters.SearchFilter(filter_predicates={"asn": {"lookup_expr": "exact", "preprocessor": dict}})
|
|
1405
1407
|
|
|
1406
|
-
params = {"q": "
|
|
1408
|
+
params = {"q": "12345"}
|
|
1407
1409
|
# Both querysets are empty so we dont use assertQuerysetEqualAndNotEmpty here.
|
|
1408
1410
|
self.assertEqual(self.get_filterset_count(params, MyLocationFilterSet2), 0)
|
|
1409
1411
|
|
|
@@ -637,11 +637,14 @@ class DynamicFilterFormTest(TestCase):
|
|
|
637
637
|
form._get_lookup_field_choices(),
|
|
638
638
|
[
|
|
639
639
|
("color", "Color"),
|
|
640
|
+
("contacts", "Contacts (name or ID)"),
|
|
640
641
|
("content_types", "Content type(s)"),
|
|
641
642
|
("created", "Created"),
|
|
643
|
+
("dynamic_groups", "Dynamic groups (name or ID)"),
|
|
642
644
|
("id", "Id"),
|
|
643
645
|
("last_updated", "Last updated"),
|
|
644
646
|
("name", "Name"),
|
|
647
|
+
("teams", "Teams (name or ID)"),
|
|
645
648
|
],
|
|
646
649
|
)
|
|
647
650
|
self.assertEqual(
|
|
@@ -655,9 +658,11 @@ class DynamicFilterFormTest(TestCase):
|
|
|
655
658
|
("contact_email", "Contact E-mail"),
|
|
656
659
|
("contact_name", "Contact name"),
|
|
657
660
|
("contact_phone", "Contact phone"),
|
|
661
|
+
("contacts", "Contacts (name or ID)"),
|
|
658
662
|
("created", "Created"),
|
|
659
663
|
("description", "Description"),
|
|
660
664
|
("devices", "Devices (name or ID)"),
|
|
665
|
+
("dynamic_groups", "Dynamic groups (name or ID)"),
|
|
661
666
|
("cf_example_app_auto_custom_field", "Example App Automatically Added Custom Field"),
|
|
662
667
|
("facility", "Facility"),
|
|
663
668
|
("has_vlan_groups", "Has VLAN groups"),
|
|
@@ -687,6 +692,7 @@ class DynamicFilterFormTest(TestCase):
|
|
|
687
692
|
("status", "Status (name or ID)"),
|
|
688
693
|
("vlans", "Tagged VLANs (VID or ID)"),
|
|
689
694
|
("tags", "Tags"),
|
|
695
|
+
("teams", "Teams (name or ID)"),
|
|
690
696
|
("tenant_id", 'Tenant (ID) (deprecated, use "tenant" filter instead)'),
|
|
691
697
|
("tenant", "Tenant (name or ID)"),
|
|
692
698
|
("tenant_group", "Tenant Group (name or ID)"),
|
|
@@ -723,11 +729,14 @@ class DynamicFilterFormTest(TestCase):
|
|
|
723
729
|
[
|
|
724
730
|
(None, "---------"),
|
|
725
731
|
("color", "Color"),
|
|
732
|
+
("contacts", "Contacts (name or ID)"),
|
|
726
733
|
("content_types", "Content type(s)"),
|
|
727
734
|
("created", "Created"),
|
|
735
|
+
("dynamic_groups", "Dynamic groups (name or ID)"),
|
|
728
736
|
("id", "Id"),
|
|
729
737
|
("last_updated", "Last updated"),
|
|
730
738
|
("name", "Name"),
|
|
739
|
+
("teams", "Teams (name or ID)"),
|
|
731
740
|
],
|
|
732
741
|
)
|
|
733
742
|
self.assertEqual(
|
|
@@ -52,6 +52,8 @@ from nautobot.dcim.models import (
|
|
|
52
52
|
InterfaceRedundancyGroupAssociation,
|
|
53
53
|
Location,
|
|
54
54
|
LocationType,
|
|
55
|
+
Manufacturer,
|
|
56
|
+
Module,
|
|
55
57
|
PowerFeed,
|
|
56
58
|
PowerOutlet,
|
|
57
59
|
PowerPanel,
|
|
@@ -93,15 +95,13 @@ User = get_user_model()
|
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
class GraphQLTestCaseBase(TestCase):
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# TODO: the below *shouldn't* be needed, but without it, when run with --parallel test flag,
|
|
99
|
-
# these tests consistently fail with schema construction errors
|
|
100
|
-
cls.SCHEMA = graphene_settings.SCHEMA # not a no-op; this causes the schema to be built
|
|
98
|
+
def setUp(self):
|
|
99
|
+
self.SCHEMA = graphene_settings.SCHEMA # not a no-op; this causes the schema to be built when first called
|
|
101
100
|
|
|
102
101
|
|
|
103
102
|
class GraphQLTestCase(GraphQLTestCaseBase):
|
|
104
103
|
def setUp(self):
|
|
104
|
+
super().setUp()
|
|
105
105
|
self.user = create_test_user("graphql_testuser")
|
|
106
106
|
GraphQLQuery.objects.create(name="GQL 1", query="{ query: locations {name} }")
|
|
107
107
|
GraphQLQuery.objects.create(name="GQL 2", query="query ($name: [String!]) { locations(name:$name) {name} }")
|
|
@@ -178,7 +178,7 @@ class GraphQLTestCase(GraphQLTestCaseBase):
|
|
|
178
178
|
for app_label, models in registry["model_features"]["graphql"].items():
|
|
179
179
|
for model_name in models:
|
|
180
180
|
model = apps.get_model(app_label=app_label, model_name=model_name)
|
|
181
|
-
self.assertIsNotNone(graphene_django_registry.get_type_for_model(model))
|
|
181
|
+
self.assertIsNotNone(graphene_django_registry.get_type_for_model(model), model)
|
|
182
182
|
|
|
183
183
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
184
184
|
def test_graphql_url_field(self):
|
|
@@ -238,6 +238,7 @@ class GraphQLGenerateSchemaTypeTestCase(GraphQLTestCaseBase):
|
|
|
238
238
|
|
|
239
239
|
class GraphQLExtendSchemaType(GraphQLTestCaseBase):
|
|
240
240
|
def setUp(self):
|
|
241
|
+
super().setUp()
|
|
241
242
|
self.datas = (
|
|
242
243
|
{"field_name": "my_text", "field_type": CustomFieldTypeChoices.TYPE_TEXT},
|
|
243
244
|
{
|
|
@@ -329,6 +330,7 @@ class GraphQLExtendSchemaType(GraphQLTestCaseBase):
|
|
|
329
330
|
|
|
330
331
|
class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
|
|
331
332
|
def setUp(self):
|
|
333
|
+
super().setUp()
|
|
332
334
|
location_ct = ContentType.objects.get_for_model(Location)
|
|
333
335
|
rack_ct = ContentType.objects.get_for_model(Rack)
|
|
334
336
|
vlan_ct = ContentType.objects.get_for_model(VLAN)
|
|
@@ -455,6 +457,7 @@ class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
|
|
|
455
457
|
|
|
456
458
|
class GraphQLSearchParameters(GraphQLTestCaseBase):
|
|
457
459
|
def setUp(self):
|
|
460
|
+
super().setUp()
|
|
458
461
|
self.schema = generate_schema_type(app_name="dcim", model=Location)
|
|
459
462
|
|
|
460
463
|
def test_search_parameters(self):
|
|
@@ -476,8 +479,6 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
|
|
|
476
479
|
@classmethod
|
|
477
480
|
def setUpTestData(cls):
|
|
478
481
|
"""Initialize the Database with some datas and multiple users associated with different permissions."""
|
|
479
|
-
super().setUpTestData()
|
|
480
|
-
|
|
481
482
|
cls.groups = (
|
|
482
483
|
Group.objects.create(name="Group 1"),
|
|
483
484
|
Group.objects.create(name="Group 2"),
|
|
@@ -705,22 +706,23 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
705
706
|
@classmethod
|
|
706
707
|
def setUpTestData(cls):
|
|
707
708
|
"""Initialize the Database with some datas."""
|
|
708
|
-
super().setUpTestData()
|
|
709
709
|
cls.user = User.objects.create(username="Super User", is_active=True, is_superuser=True)
|
|
710
710
|
|
|
711
|
-
# Remove random IPAddress and Device fixtures for this custom test
|
|
711
|
+
# Remove random IPAddress, Module and Device fixtures for this custom test
|
|
712
712
|
IPAddress.objects.all().delete()
|
|
713
713
|
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
714
714
|
Device.objects.all().delete()
|
|
715
|
+
Module.objects.all().delete()
|
|
715
716
|
|
|
716
717
|
# Initialize fake request that will be required to execute GraphQL query
|
|
717
718
|
cls.request = RequestFactory().request(SERVER_NAME="WebRequestContext")
|
|
718
719
|
cls.request.id = uuid.uuid4()
|
|
719
720
|
cls.request.user = cls.user
|
|
720
721
|
|
|
721
|
-
# Populate Data
|
|
722
|
-
|
|
723
|
-
cls.
|
|
722
|
+
# Populate Data - create new device types with no component templates
|
|
723
|
+
manufacturer = Manufacturer.objects.first()
|
|
724
|
+
cls.device_type1 = DeviceType.objects.create(manufacturer=manufacturer, model="test device_type1")
|
|
725
|
+
cls.device_type2 = DeviceType.objects.create(manufacturer=manufacturer, model="test device_type2")
|
|
724
726
|
roles = Role.objects.get_for_model(Device)
|
|
725
727
|
cls.device_role1 = roles[0]
|
|
726
728
|
cls.device_role2 = roles[1]
|
|
@@ -863,6 +865,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
863
865
|
]
|
|
864
866
|
|
|
865
867
|
interface_status = Status.objects.get_for_model(Interface).first()
|
|
868
|
+
interface_role = Role.objects.get_for_model(Interface).first()
|
|
866
869
|
cls.interface11 = Interface.objects.create(
|
|
867
870
|
name="Int1",
|
|
868
871
|
type=InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
@@ -871,12 +874,14 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
871
874
|
mode=InterfaceModeChoices.MODE_ACCESS,
|
|
872
875
|
untagged_vlan=cls.vlan1,
|
|
873
876
|
status=interface_status,
|
|
877
|
+
role=interface_role,
|
|
874
878
|
)
|
|
875
879
|
cls.interface12 = Interface.objects.create(
|
|
876
880
|
name="Int2",
|
|
877
881
|
type=InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
878
882
|
device=cls.device1,
|
|
879
883
|
status=interface_status,
|
|
884
|
+
role=interface_role,
|
|
880
885
|
)
|
|
881
886
|
cls.namespace = Namespace.objects.first()
|
|
882
887
|
cls.intr_group_status = Status.objects.get_for_model(InterfaceRedundancyGroup).first()
|
|
@@ -958,6 +963,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
958
963
|
untagged_vlan=cls.vlan2,
|
|
959
964
|
mode=InterfaceModeChoices.MODE_ACCESS,
|
|
960
965
|
status=interface_status,
|
|
966
|
+
role=interface_role,
|
|
961
967
|
)
|
|
962
968
|
cls.interface22 = Interface.objects.create(
|
|
963
969
|
name="Int2",
|
|
@@ -984,7 +990,11 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
984
990
|
)
|
|
985
991
|
|
|
986
992
|
cls.interface31 = Interface.objects.create(
|
|
987
|
-
name="Int1",
|
|
993
|
+
name="Int1",
|
|
994
|
+
type=InterfaceTypeChoices.TYPE_VIRTUAL,
|
|
995
|
+
device=cls.device3,
|
|
996
|
+
status=interface_status,
|
|
997
|
+
role=interface_role,
|
|
988
998
|
)
|
|
989
999
|
cls.interface31 = Interface.objects.create(
|
|
990
1000
|
name="Mgmt1",
|
|
@@ -993,6 +1003,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
993
1003
|
mgmt_only=True,
|
|
994
1004
|
enabled=False,
|
|
995
1005
|
status=interface_status,
|
|
1006
|
+
role=interface_role,
|
|
996
1007
|
)
|
|
997
1008
|
|
|
998
1009
|
cable_statuses = Status.objects.get_for_model(Cable)
|
|
@@ -2197,7 +2208,7 @@ query {
|
|
|
2197
2208
|
self.assertIsNone(result.errors)
|
|
2198
2209
|
self.assertIsInstance(result.data, dict, result)
|
|
2199
2210
|
self.assertIsInstance(result.data["device_types"], list, result)
|
|
2200
|
-
self.assertEqual(result.data["device_types"][0]["model"],
|
|
2211
|
+
self.assertEqual(result.data["device_types"][0]["model"], DeviceType.objects.first().model, result)
|
|
2201
2212
|
|
|
2202
2213
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2203
2214
|
def test_query_interface_pagination(self):
|
|
@@ -2233,7 +2244,7 @@ query {
|
|
|
2233
2244
|
self.assertEqual(interface_names, ["Int2", "Int1"])
|
|
2234
2245
|
|
|
2235
2246
|
result_2 = self.execute_query(query_all)
|
|
2236
|
-
self.assertEqual(len(result_2.data.get("interfaces", [])),
|
|
2247
|
+
self.assertEqual(len(result_2.data.get("interfaces", [])), Interface.objects.count())
|
|
2237
2248
|
|
|
2238
2249
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
2239
2250
|
def test_query_power_feeds_cable_peer(self):
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
from datetime import timedelta
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from django.core.cache import cache
|
|
4
6
|
from django.core.files.base import ContentFile
|
|
7
|
+
from django.utils import timezone
|
|
5
8
|
import yaml
|
|
6
9
|
|
|
10
|
+
from nautobot.core.jobs.cleanup import CleanupTypes
|
|
7
11
|
from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
|
|
8
12
|
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
9
13
|
from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
|
|
14
|
+
from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
|
|
10
15
|
from nautobot.extras.models import (
|
|
11
16
|
Contact,
|
|
12
17
|
ContactAssociation,
|
|
13
18
|
ExportTemplate,
|
|
14
19
|
FileProxy,
|
|
15
20
|
JobLogEntry,
|
|
21
|
+
JobResult,
|
|
22
|
+
ObjectChange,
|
|
16
23
|
Role,
|
|
17
24
|
Status,
|
|
18
25
|
)
|
|
@@ -413,3 +420,118 @@ class ImportObjectsTestCase(TransactionTestCase):
|
|
|
413
420
|
)
|
|
414
421
|
|
|
415
422
|
self.assertEqual(associations_job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class LogsCleanupTestCase(TransactionTestCase):
|
|
426
|
+
"""
|
|
427
|
+
Test the LogsCleanup system job.
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
databases = ("default", "job_logs")
|
|
431
|
+
|
|
432
|
+
def setUp(self):
|
|
433
|
+
super().setUp()
|
|
434
|
+
# Recall that TransactionTestCase truncates the DB after each test case...
|
|
435
|
+
cache.delete("nautobot.extras.utils.change_logged_models_queryset")
|
|
436
|
+
if ObjectChange.objects.count() < 2:
|
|
437
|
+
ObjectChangeFactory.create_batch(40)
|
|
438
|
+
if JobResult.objects.count() < 2:
|
|
439
|
+
JobResultFactory.create_batch(20)
|
|
440
|
+
|
|
441
|
+
def test_cleanup_without_permission(self):
|
|
442
|
+
"""Job should enforce user permissions on the content-types being deleted."""
|
|
443
|
+
job_result_count = JobResult.objects.count()
|
|
444
|
+
job_log_entry_count = JobLogEntry.objects.count()
|
|
445
|
+
object_change_count = ObjectChange.objects.count()
|
|
446
|
+
|
|
447
|
+
job_result = create_job_result_and_run_job(
|
|
448
|
+
"nautobot.core.jobs.cleanup",
|
|
449
|
+
"LogsCleanup",
|
|
450
|
+
username=self.user.username,
|
|
451
|
+
cleanup_types=[CleanupTypes.JOB_RESULT],
|
|
452
|
+
max_age=0,
|
|
453
|
+
)
|
|
454
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
455
|
+
log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
456
|
+
self.assertEqual(log_error.message, f'User "{self.user}" does not have permission to delete JobResult records')
|
|
457
|
+
self.assertEqual(JobResult.objects.count(), job_result_count + 1)
|
|
458
|
+
self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
|
|
459
|
+
self.assertEqual(ObjectChange.objects.count(), object_change_count)
|
|
460
|
+
|
|
461
|
+
job_result = create_job_result_and_run_job(
|
|
462
|
+
"nautobot.core.jobs.cleanup",
|
|
463
|
+
"LogsCleanup",
|
|
464
|
+
username=self.user.username,
|
|
465
|
+
cleanup_types=[CleanupTypes.OBJECT_CHANGE],
|
|
466
|
+
max_age=0,
|
|
467
|
+
)
|
|
468
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_FAILURE)
|
|
469
|
+
log_error = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_ERROR)
|
|
470
|
+
self.assertEqual(
|
|
471
|
+
log_error.message, f'User "{self.user}" does not have permission to delete ObjectChange records'
|
|
472
|
+
)
|
|
473
|
+
self.assertEqual(JobResult.objects.count(), job_result_count + 2)
|
|
474
|
+
self.assertGreater(JobLogEntry.objects.count(), job_log_entry_count)
|
|
475
|
+
self.assertEqual(ObjectChange.objects.count(), object_change_count)
|
|
476
|
+
|
|
477
|
+
def test_cleanup_with_constrained_permission(self):
|
|
478
|
+
"""Job should only allow the user to cleanup records they have permission to delete."""
|
|
479
|
+
job_result_1 = JobResult.objects.last()
|
|
480
|
+
job_result_2 = JobResult.objects.first()
|
|
481
|
+
self.assertNotEqual(job_result_1.pk, job_result_2.pk)
|
|
482
|
+
object_change_1 = ObjectChange.objects.first()
|
|
483
|
+
object_change_2 = ObjectChange.objects.last()
|
|
484
|
+
self.assertNotEqual(object_change_1.pk, object_change_2.pk)
|
|
485
|
+
obj_perm = ObjectPermission(
|
|
486
|
+
name="Test permission",
|
|
487
|
+
constraints={"pk__in": [str(job_result_1.pk), str(object_change_1.pk)]},
|
|
488
|
+
actions=["delete"],
|
|
489
|
+
)
|
|
490
|
+
obj_perm.save()
|
|
491
|
+
obj_perm.users.add(self.user)
|
|
492
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(JobResult))
|
|
493
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(ObjectChange))
|
|
494
|
+
job_result = create_job_result_and_run_job(
|
|
495
|
+
"nautobot.core.jobs.cleanup",
|
|
496
|
+
"LogsCleanup",
|
|
497
|
+
username=self.user.username,
|
|
498
|
+
cleanup_types=[CleanupTypes.JOB_RESULT, CleanupTypes.OBJECT_CHANGE],
|
|
499
|
+
max_age=0,
|
|
500
|
+
)
|
|
501
|
+
self.assertEqual(job_result.status, JobResultStatusChoices.STATUS_SUCCESS)
|
|
502
|
+
self.assertEqual(job_result.result["extras.JobResult"], 1)
|
|
503
|
+
self.assertEqual(job_result.result["extras.ObjectChange"], 1)
|
|
504
|
+
with self.assertRaises(JobResult.DoesNotExist):
|
|
505
|
+
JobResult.objects.get(pk=job_result_1.pk)
|
|
506
|
+
JobResult.objects.get(pk=job_result_2.pk)
|
|
507
|
+
with self.assertRaises(ObjectChange.DoesNotExist):
|
|
508
|
+
ObjectChange.objects.get(pk=object_change_1.pk)
|
|
509
|
+
ObjectChange.objects.get(pk=object_change_2.pk)
|
|
510
|
+
|
|
511
|
+
def test_cleanup_job_results(self):
|
|
512
|
+
"""With unconstrained permissions, all JobResults before the cutoff should be deleted."""
|
|
513
|
+
cutoff = timezone.now() - timedelta(days=60)
|
|
514
|
+
create_job_result_and_run_job(
|
|
515
|
+
"nautobot.core.jobs.cleanup",
|
|
516
|
+
"LogsCleanup",
|
|
517
|
+
cleanup_types=[CleanupTypes.JOB_RESULT],
|
|
518
|
+
max_age=60,
|
|
519
|
+
)
|
|
520
|
+
self.assertFalse(JobResult.objects.filter(date_done__lt=cutoff).exists())
|
|
521
|
+
self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
|
|
522
|
+
self.assertTrue(ObjectChange.objects.filter(time__lt=cutoff).exists())
|
|
523
|
+
self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
|
|
524
|
+
|
|
525
|
+
def test_cleanup_object_changes(self):
|
|
526
|
+
"""With unconstrained permissions, all ObjectChanges before the cutoff should be deleted."""
|
|
527
|
+
cutoff = timezone.now() - timedelta(days=60)
|
|
528
|
+
create_job_result_and_run_job(
|
|
529
|
+
"nautobot.core.jobs.cleanup",
|
|
530
|
+
"LogsCleanup",
|
|
531
|
+
cleanup_types=[CleanupTypes.OBJECT_CHANGE],
|
|
532
|
+
max_age=60,
|
|
533
|
+
)
|
|
534
|
+
self.assertTrue(JobResult.objects.filter(date_done__lt=cutoff).exists())
|
|
535
|
+
self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
|
|
536
|
+
self.assertFalse(ObjectChange.objects.filter(time__lt=cutoff).exists())
|
|
537
|
+
self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
|