nautobot 2.3.15b1__py3-none-any.whl → 2.4.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/__init__.py +15 -0
- nautobot/apps/__init__.py +1 -1
- nautobot/apps/api.py +8 -10
- nautobot/apps/change_logging.py +2 -2
- nautobot/apps/choices.py +4 -4
- nautobot/apps/config.py +32 -3
- nautobot/apps/events.py +19 -0
- nautobot/apps/exceptions.py +0 -2
- nautobot/apps/factory.py +2 -2
- nautobot/apps/filters.py +1 -1
- nautobot/apps/forms.py +20 -20
- nautobot/apps/graphql.py +2 -2
- nautobot/apps/jobs.py +8 -8
- nautobot/apps/models.py +19 -19
- nautobot/apps/tables.py +1 -1
- nautobot/apps/testing.py +10 -10
- nautobot/apps/ui.py +44 -9
- nautobot/apps/utils.py +7 -15
- nautobot/apps/views.py +8 -6
- nautobot/circuits/api/serializers.py +1 -0
- nautobot/circuits/api/views.py +4 -8
- nautobot/circuits/navigation.py +0 -57
- nautobot/circuits/templates/circuits/circuit_create.html +1 -7
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -71
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -64
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +40 -0
- nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +26 -0
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -76
- nautobot/circuits/tests/integration/test_relationships.py +33 -24
- nautobot/circuits/tests/test_filters.py +4 -8
- nautobot/circuits/views.py +143 -26
- nautobot/cloud/api/views.py +6 -10
- nautobot/cloud/factory.py +4 -1
- nautobot/cloud/models.py +1 -1
- nautobot/cloud/tests/test_filters.py +5 -4
- nautobot/cloud/views.py +0 -16
- nautobot/core/api/constants.py +11 -0
- nautobot/core/api/fields.py +5 -5
- nautobot/core/api/filter_backends.py +3 -9
- nautobot/core/api/metadata.py +28 -256
- nautobot/core/api/pagination.py +3 -2
- nautobot/core/api/renderers.py +3 -0
- nautobot/core/api/schema.py +13 -2
- nautobot/core/api/serializers.py +54 -268
- nautobot/core/api/urls.py +3 -4
- nautobot/core/api/utils.py +0 -62
- nautobot/core/api/views.py +102 -159
- nautobot/core/apps/__init__.py +22 -575
- nautobot/core/celery/__init__.py +13 -0
- nautobot/core/celery/schedulers.py +48 -3
- nautobot/core/choices.py +2 -2
- nautobot/core/cli/__init__.py +8 -0
- nautobot/core/constants.py +7 -0
- nautobot/core/events/__init__.py +116 -0
- nautobot/core/events/base.py +27 -0
- nautobot/core/events/exceptions.py +10 -0
- nautobot/core/events/redis_broker.py +48 -0
- nautobot/core/events/syslog_broker.py +19 -0
- nautobot/core/exceptions.py +0 -6
- nautobot/core/filters.py +19 -16
- nautobot/core/forms/__init__.py +19 -19
- nautobot/core/forms/fields.py +62 -14
- nautobot/core/forms/forms.py +33 -2
- nautobot/core/forms/utils.py +2 -1
- nautobot/core/graphql/schema.py +3 -1
- nautobot/core/graphql/types.py +1 -1
- nautobot/core/jobs/__init__.py +28 -7
- nautobot/core/jobs/bulk_actions.py +248 -0
- nautobot/core/jobs/cleanup.py +2 -2
- nautobot/core/jobs/groups.py +1 -1
- nautobot/core/management/commands/generate_test_data.py +21 -0
- nautobot/core/management/commands/validate_models.py +1 -1
- nautobot/core/middleware.py +16 -0
- nautobot/core/models/__init__.py +1 -1
- nautobot/core/models/fields.py +11 -7
- nautobot/core/models/query_functions.py +2 -2
- nautobot/core/models/tree_queries.py +2 -2
- nautobot/core/settings.py +71 -4
- nautobot/core/settings.yaml +107 -0
- nautobot/core/tables.py +15 -51
- nautobot/core/tasks.py +1 -1
- nautobot/core/templates/about.html +67 -0
- nautobot/core/templates/components/button/default.html +7 -0
- nautobot/core/templates/components/button/dropdown.html +20 -0
- nautobot/core/templates/components/layout/one_over_two.html +19 -0
- nautobot/core/templates/components/layout/two_over_one.html +19 -0
- nautobot/core/templates/components/panel/body_content_data_table.html +27 -0
- nautobot/core/templates/components/panel/body_content_objects_table.html +4 -0
- nautobot/core/templates/components/panel/body_content_tags.html +6 -0
- nautobot/core/templates/components/panel/body_content_text.html +12 -0
- nautobot/core/templates/components/panel/body_wrapper_generic.html +3 -0
- nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +3 -0
- nautobot/core/templates/components/panel/body_wrapper_table.html +3 -0
- nautobot/core/templates/components/panel/footer_contacts_table.html +20 -0
- nautobot/core/templates/components/panel/footer_content_table.html +14 -0
- nautobot/core/templates/components/panel/grouping_toggle.html +14 -0
- nautobot/core/templates/components/panel/header_extra_content_table.html +3 -0
- nautobot/core/templates/components/panel/panel.html +16 -0
- nautobot/core/templates/components/panel/stats_panel_body.html +8 -0
- nautobot/core/templates/components/tab/content_wrapper.html +3 -0
- nautobot/core/templates/components/tab/label_wrapper.html +5 -0
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +3 -0
- nautobot/core/templates/generic/object_retrieve.html +28 -17
- nautobot/core/templates/inc/computed_fields/panel_data.html +4 -7
- nautobot/core/templates/inc/custom_fields/panel.html +2 -2
- nautobot/core/templates/inc/custom_fields/panel_data.html +4 -7
- nautobot/core/templates/inc/footer.html +1 -0
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/inc/nav_menu.html +2 -1
- nautobot/core/templates/inc/relationships_panel.html +1 -1
- nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
- nautobot/core/templates/inc/tenant_table_row.html +11 -0
- nautobot/core/templates/nautobot_config.py.j2 +16 -0
- nautobot/core/templates/panel_table.html +12 -0
- nautobot/core/templates/utilities/render_jinja2.html +117 -0
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/theme_preview.html +7 -0
- nautobot/core/templatetags/helpers.py +104 -6
- nautobot/core/templatetags/ui_framework.py +40 -0
- nautobot/core/testing/__init__.py +8 -8
- nautobot/core/testing/api.py +187 -137
- nautobot/core/testing/context.py +18 -0
- nautobot/core/testing/filters.py +44 -34
- nautobot/core/testing/forms.py +2 -0
- nautobot/core/testing/views.py +68 -146
- nautobot/core/tests/integration/test_app_home.py +0 -1
- nautobot/core/tests/integration/test_app_navbar.py +0 -1
- nautobot/core/tests/integration/test_filters.py +0 -2
- nautobot/core/tests/integration/test_home.py +0 -1
- nautobot/core/tests/integration/test_navbar.py +0 -1
- nautobot/core/tests/integration/test_view_authentication.py +2 -1
- nautobot/core/tests/nautobot_config.py +198 -0
- nautobot/core/tests/runner.py +3 -3
- nautobot/core/tests/test_api.py +154 -176
- nautobot/core/tests/test_events.py +214 -0
- nautobot/core/tests/test_forms.py +1 -0
- nautobot/core/tests/test_jinja_filters.py +1 -0
- nautobot/core/tests/test_jobs.py +387 -14
- nautobot/core/tests/test_navigations.py +7 -241
- nautobot/core/tests/test_settings_schema.py +7 -0
- nautobot/core/tests/test_tables.py +100 -0
- nautobot/core/tests/test_templatetags_helpers.py +16 -0
- nautobot/core/tests/test_ui.py +150 -0
- nautobot/core/tests/test_utils.py +55 -18
- nautobot/core/tests/test_views.py +153 -5
- nautobot/core/ui/__init__.py +0 -0
- nautobot/core/ui/base.py +11 -0
- nautobot/core/ui/choices.py +44 -0
- nautobot/core/ui/homepage.py +167 -0
- nautobot/core/ui/nav.py +280 -0
- nautobot/core/ui/object_detail.py +1855 -0
- nautobot/core/ui/utils.py +36 -0
- nautobot/core/urls.py +15 -0
- nautobot/core/utils/config.py +30 -3
- nautobot/core/utils/lookup.py +12 -2
- nautobot/core/utils/querysets.py +64 -0
- nautobot/core/utils/requests.py +24 -9
- nautobot/core/views/__init__.py +48 -1
- nautobot/core/views/generic.py +82 -177
- nautobot/core/views/mixins.py +98 -38
- nautobot/core/views/paginator.py +8 -5
- nautobot/core/views/renderers.py +9 -9
- nautobot/core/views/utils.py +11 -0
- nautobot/core/wsgi.py +3 -3
- nautobot/dcim/api/serializers.py +82 -189
- nautobot/dcim/api/urls.py +5 -0
- nautobot/dcim/api/views.py +57 -110
- nautobot/dcim/apps.py +1 -0
- nautobot/dcim/choices.py +28 -0
- nautobot/dcim/factory.py +58 -0
- nautobot/dcim/filters/__init__.py +204 -2
- nautobot/dcim/forms.py +221 -9
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/migrations/0063_interfacevdcassignment_virtualdevicecontext_and_more.py +165 -0
- nautobot/dcim/migrations/0064_virtualdevicecontext_status_data_migration.py +28 -0
- nautobot/dcim/migrations/0065_controller_capabilities_and_more.py +29 -0
- nautobot/dcim/migrations/0066_controllermanageddevicegroup_radio_profiles_and_more.py +33 -0
- nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
- nautobot/dcim/models/__init__.py +5 -1
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +22 -20
- nautobot/dcim/models/devices.py +181 -3
- nautobot/dcim/models/locations.py +3 -3
- nautobot/dcim/models/power.py +6 -5
- nautobot/dcim/models/racks.py +6 -6
- nautobot/dcim/navigation.py +25 -224
- nautobot/dcim/signals.py +44 -0
- nautobot/dcim/tables/__init__.py +5 -3
- nautobot/dcim/tables/devices.py +103 -7
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/tables/racks.py +1 -1
- nautobot/dcim/templates/dcim/controller/base.html +10 -0
- nautobot/dcim/templates/dcim/controller_create.html +2 -7
- nautobot/dcim/templates/dcim/controller_retrieve.html +6 -10
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +25 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +68 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +51 -0
- nautobot/dcim/templates/dcim/device/base.html +6 -42
- nautobot/dcim/templates/dcim/device/wireless.html +73 -0
- nautobot/dcim/templates/dcim/device.html +4 -10
- nautobot/dcim/templates/dcim/device_edit.html +36 -37
- nautobot/dcim/templates/dcim/interface.html +1 -0
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/location.html +1 -9
- nautobot/dcim/templates/dcim/location_edit.html +1 -7
- nautobot/dcim/templates/dcim/locationtype.html +0 -107
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +8 -0
- nautobot/dcim/templates/dcim/rack.html +1 -9
- nautobot/dcim/templates/dcim/rack_edit.html +1 -7
- nautobot/dcim/templates/dcim/rackreservation.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +68 -0
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +28 -0
- nautobot/dcim/tests/integration/test_controller.py +62 -0
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
- nautobot/dcim/tests/test_api.py +188 -64
- nautobot/dcim/tests/test_filters.py +172 -76
- nautobot/dcim/tests/test_graphql.py +52 -0
- nautobot/dcim/tests/test_jobs.py +118 -0
- nautobot/dcim/tests/test_models.py +159 -5
- nautobot/dcim/tests/test_signals.py +1 -0
- nautobot/dcim/tests/test_views.py +118 -88
- nautobot/dcim/urls.py +72 -27
- nautobot/dcim/utils.py +2 -2
- nautobot/dcim/views.py +357 -62
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +111 -87
- nautobot/extras/api/urls.py +4 -0
- nautobot/extras/api/views.py +93 -35
- nautobot/extras/choices.py +13 -0
- nautobot/extras/constants.py +2 -1
- nautobot/extras/context_managers.py +23 -6
- nautobot/extras/datasources/git.py +4 -1
- nautobot/extras/factory.py +27 -0
- nautobot/extras/filters/__init__.py +66 -5
- nautobot/extras/forms/base.py +2 -2
- nautobot/extras/forms/forms.py +262 -59
- nautobot/extras/forms/mixins.py +3 -3
- nautobot/extras/graphql/types.py +25 -1
- nautobot/extras/health_checks.py +1 -2
- nautobot/extras/jobs.py +114 -20
- nautobot/extras/management/__init__.py +1 -0
- nautobot/extras/management/commands/runjob.py +7 -79
- nautobot/extras/management/commands/runjob_with_job_result.py +46 -0
- nautobot/extras/management/utils.py +87 -0
- nautobot/extras/managers.py +3 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
- nautobot/extras/migrations/0117_create_job_queue_model.py +129 -0
- nautobot/extras/migrations/0118_task_queue_to_job_queue_migration.py +78 -0
- nautobot/extras/migrations/0119_remove_task_queues_from_job_and_queue_from_scheduled_job.py +28 -0
- nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
- nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
- nautobot/extras/models/__init__.py +5 -1
- nautobot/extras/models/change_logging.py +7 -3
- nautobot/extras/models/contacts.py +1 -1
- nautobot/extras/models/customfields.py +12 -11
- nautobot/extras/models/groups.py +11 -9
- nautobot/extras/models/jobs.py +237 -37
- nautobot/extras/models/models.py +2 -2
- nautobot/extras/models/relationships.py +69 -1
- nautobot/extras/models/secrets.py +5 -0
- nautobot/extras/navigation.py +20 -262
- nautobot/extras/plugins/__init__.py +54 -19
- nautobot/extras/plugins/marketplace_manifest.yml +455 -0
- nautobot/extras/plugins/tables.py +16 -14
- nautobot/extras/plugins/urls.py +1 -0
- nautobot/extras/plugins/views.py +104 -61
- nautobot/extras/registry.py +1 -1
- nautobot/extras/secrets/__init__.py +2 -2
- nautobot/extras/signals.py +39 -1
- nautobot/extras/tables.py +42 -6
- nautobot/extras/templates/extras/dynamicgroup.html +1 -9
- nautobot/extras/templates/extras/externalintegration_retrieve.html +0 -47
- nautobot/extras/templates/extras/inc/tags_panel.html +1 -5
- nautobot/extras/templates/extras/job_bulk_edit.html +2 -1
- nautobot/extras/templates/extras/job_detail.html +52 -6
- nautobot/extras/templates/extras/job_edit.html +6 -2
- nautobot/extras/templates/extras/job_list.html +2 -7
- nautobot/extras/templates/extras/jobqueue_retrieve.html +36 -0
- nautobot/extras/templates/extras/marketplace.html +296 -0
- nautobot/extras/templates/extras/plugin_detail.html +32 -15
- nautobot/extras/templates/extras/plugins_list.html +35 -1
- nautobot/extras/templates/extras/plugins_tiles.html +90 -0
- nautobot/extras/templates/extras/role_retrieve.html +16 -0
- nautobot/extras/templates/extras/secret.html +0 -65
- nautobot/extras/templates/extras/secret_check.js +16 -0
- nautobot/extras/templates/extras/secret_create.html +114 -0
- nautobot/extras/templates/extras/secret_edit.html +1 -114
- nautobot/extras/templates/extras/secretsgroup_edit.html +1 -1
- nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -0
- nautobot/extras/templatetags/job_buttons.py +5 -4
- nautobot/extras/templatetags/plugins.py +69 -6
- nautobot/extras/test_jobs/api_test_job.py +1 -1
- nautobot/extras/test_jobs/atomic_transaction.py +2 -2
- nautobot/extras/test_jobs/dry_run.py +1 -1
- nautobot/extras/test_jobs/fail.py +5 -5
- nautobot/extras/test_jobs/file_output.py +1 -1
- nautobot/extras/test_jobs/file_upload_fail.py +1 -1
- nautobot/extras/test_jobs/file_upload_pass.py +1 -1
- nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
- nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
- nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
- nautobot/extras/test_jobs/log_redaction.py +1 -1
- nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
- nautobot/extras/test_jobs/modify_db.py +1 -1
- nautobot/extras/test_jobs/object_var_optional.py +1 -1
- nautobot/extras/test_jobs/object_var_required.py +1 -1
- nautobot/extras/test_jobs/object_vars.py +1 -1
- nautobot/extras/test_jobs/pass.py +3 -3
- nautobot/extras/test_jobs/profiling.py +1 -1
- nautobot/extras/test_jobs/relative_import.py +3 -3
- nautobot/extras/test_jobs/singleton.py +16 -0
- nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
- nautobot/extras/test_jobs/task_queues.py +1 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
- nautobot/extras/tests/test_api.py +157 -55
- nautobot/extras/tests/test_context_managers.py +4 -1
- nautobot/extras/tests/test_customfields.py +1 -1
- nautobot/extras/tests/test_datasources.py +2 -1
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_filters.py +219 -535
- nautobot/extras/tests/test_forms.py +20 -1
- nautobot/extras/tests/test_job_variables.py +73 -152
- nautobot/extras/tests/test_jobs.py +192 -62
- nautobot/extras/tests/test_models.py +71 -16
- nautobot/extras/tests/test_plugins.py +62 -9
- nautobot/extras/tests/test_relationships.py +124 -10
- nautobot/extras/tests/test_utils.py +23 -2
- nautobot/extras/tests/test_views.py +162 -161
- nautobot/extras/tests/test_webhooks.py +2 -1
- nautobot/extras/urls.py +2 -20
- nautobot/extras/utils.py +119 -4
- nautobot/extras/views.py +188 -141
- nautobot/extras/webhooks.py +5 -2
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +44 -137
- nautobot/ipam/api/views.py +68 -110
- nautobot/ipam/factory.py +1 -1
- nautobot/ipam/filters.py +3 -2
- nautobot/ipam/models.py +10 -12
- nautobot/ipam/navigation.py +0 -90
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tables.py +3 -1
- nautobot/ipam/templates/ipam/ipaddress.html +1 -9
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
- nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
- nautobot/ipam/templates/ipam/prefix.html +1 -9
- nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
- nautobot/ipam/templates/ipam/routetarget.html +0 -28
- nautobot/ipam/templates/ipam/vlan.html +1 -9
- nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
- nautobot/ipam/templates/ipam/vrf.html +0 -47
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
- nautobot/ipam/tests/test_api.py +19 -6
- nautobot/ipam/tests/test_filters.py +39 -119
- nautobot/ipam/tests/test_forms.py +49 -47
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_models.py +56 -36
- nautobot/ipam/tests/test_querysets.py +14 -0
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/ipam/urls.py +3 -69
- nautobot/ipam/utils/__init__.py +16 -10
- nautobot/ipam/views.py +91 -162
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
- nautobot/project-static/css/base.css +38 -3
- nautobot/project-static/docs/404.html +463 -19
- nautobot/project-static/docs/apps/index.html +463 -19
- nautobot/project-static/docs/apps/nautobot-apps.html +464 -21
- nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
- nautobot/project-static/docs/assets/extra.css +5 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +479 -25
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +476 -22
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +792 -291
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +507 -33
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +512 -36
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +473 -22
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +469 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +499 -35
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9883 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +525 -77
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +548 -53
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +672 -96
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1033 -180
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +526 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +876 -190
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +957 -237
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +477 -23
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +488 -30
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +663 -101
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +949 -481
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +6427 -1236
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +476 -22
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +879 -346
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +830 -173
- nautobot/project-static/docs/development/apps/api/configuration-view.html +463 -19
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +463 -19
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +463 -19
- nautobot/project-static/docs/development/apps/api/models/global-search.html +463 -19
- nautobot/project-static/docs/development/apps/api/models/graphql.html +463 -19
- nautobot/project-static/docs/development/apps/api/models/index.html +463 -19
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +463 -19
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +463 -19
- nautobot/project-static/docs/development/apps/api/prometheus.html +463 -19
- nautobot/project-static/docs/development/apps/api/setup.html +467 -155
- nautobot/project-static/docs/development/apps/api/testing.html +463 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +463 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +463 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +463 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +463 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +743 -130
- nautobot/project-static/docs/development/apps/api/views/base-template.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/index.html +465 -20
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +467 -19
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +493 -19
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/notes.html +463 -19
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +469 -21
- nautobot/project-static/docs/development/apps/api/views/urls.html +463 -19
- nautobot/project-static/docs/development/apps/index.html +463 -19
- nautobot/project-static/docs/development/apps/migration/code-updates.html +464 -52
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +464 -20
- nautobot/project-static/docs/development/apps/migration/from-v1.html +463 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +463 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +463 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +463 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +466 -22
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9261 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +9375 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +9671 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +9559 -0
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +466 -22
- nautobot/project-static/docs/development/core/application-registry.html +463 -19
- nautobot/project-static/docs/development/core/best-practices.html +463 -19
- nautobot/project-static/docs/development/core/bootstrap-ui.html +463 -19
- nautobot/project-static/docs/development/core/caching.html +463 -19
- nautobot/project-static/docs/development/core/controllers.html +465 -19
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +491 -90
- nautobot/project-static/docs/development/core/generic-views.html +463 -19
- nautobot/project-static/docs/development/core/getting-started.html +541 -129
- nautobot/project-static/docs/development/core/homepage.html +474 -30
- nautobot/project-static/docs/development/core/index.html +463 -19
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9754 -0
- nautobot/project-static/docs/development/core/model-checklist.html +473 -27
- nautobot/project-static/docs/development/core/model-features.html +463 -19
- nautobot/project-static/docs/development/core/natural-keys.html +463 -19
- nautobot/project-static/docs/development/core/navigation-menu.html +480 -26
- nautobot/project-static/docs/development/core/release-checklist.html +480 -48
- nautobot/project-static/docs/development/core/role-internals.html +463 -19
- nautobot/project-static/docs/development/core/settings.html +463 -19
- nautobot/project-static/docs/development/core/style-guide.html +466 -22
- nautobot/project-static/docs/development/core/templates.html +473 -22
- nautobot/project-static/docs/development/core/testing.html +463 -19
- nautobot/project-static/docs/development/core/ui-component-framework.html +11116 -0
- nautobot/project-static/docs/development/core/user-preferences.html +466 -22
- nautobot/project-static/docs/development/index.html +463 -19
- nautobot/project-static/docs/development/jobs/index.html +501 -21
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +463 -19
- nautobot/project-static/docs/index.html +471 -38
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
- nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/basic-panel-layout.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/button-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/dropdown-button-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-1.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/grouped-key-value-table-panel-example-2.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/table-panels-family.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/text-panels-family.png +0 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/ui-framework-example.png +0 -0
- nautobot/project-static/docs/media/models/virtual_device_context_overview.drawio +73 -0
- nautobot/project-static/docs/media/models/virtual_device_context_overview.png +0 -0
- nautobot/project-static/docs/models/dcim/virtualdevicecontext.html +14 -0
- nautobot/project-static/docs/models/extras/jobqueue.html +14 -0
- nautobot/project-static/docs/models/wireless/radioprofile.html +14 -0
- nautobot/project-static/docs/models/wireless/supporteddatarate.html +14 -0
- nautobot/project-static/docs/models/wireless/wirelessnetwork.html +14 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +469 -23
- nautobot/project-static/docs/overview/design_philosophy.html +463 -19
- nautobot/project-static/docs/release-notes/index.html +485 -22
- nautobot/project-static/docs/release-notes/version-1.0.html +651 -208
- nautobot/project-static/docs/release-notes/version-1.1.html +648 -205
- nautobot/project-static/docs/release-notes/version-1.2.html +723 -280
- nautobot/project-static/docs/release-notes/version-1.3.html +749 -306
- nautobot/project-static/docs/release-notes/version-1.4.html +834 -392
- nautobot/project-static/docs/release-notes/version-1.5.html +1022 -581
- nautobot/project-static/docs/release-notes/version-1.6.html +942 -518
- nautobot/project-static/docs/release-notes/version-2.0.html +945 -504
- nautobot/project-static/docs/release-notes/version-2.1.html +780 -339
- nautobot/project-static/docs/release-notes/version-2.2.html +773 -332
- nautobot/project-static/docs/release-notes/version-2.3.html +1142 -448
- nautobot/project-static/docs/release-notes/version-2.4.html +10323 -0
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +342 -270
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +463 -19
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +463 -19
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +463 -19
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +475 -32
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +463 -19
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +894 -180
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +476 -29
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +463 -19
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +465 -21
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +463 -19
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +463 -19
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +463 -19
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +483 -23
- nautobot/project-static/docs/user-guide/administration/installation/index.html +468 -20
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +464 -20
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +463 -19
- nautobot/project-static/docs/user-guide/administration/installation/services.html +463 -19
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +463 -19
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +484 -41
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +477 -66
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +477 -66
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +466 -23
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-nautobot-app-location.yaml +0 -16
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +463 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +469 -21
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +499 -20
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +489 -22
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9375 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +470 -30
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +466 -22
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +463 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +466 -22
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9313 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9217 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9211 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9277 -0
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +468 -22
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +463 -19
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +466 -22
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9444 -0
- nautobot/project-static/docs/user-guide/index.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +467 -23
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +9617 -0
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +472 -23
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9224 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9722 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +467 -23
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9292 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +511 -40
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +494 -23
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +466 -22
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +531 -56
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +463 -19
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +463 -19
- nautobot/project-static/img/jinja_logo.svg +97 -0
- nautobot/project-static/js/forms.js +6 -1
- nautobot/project-static/js/nav_menu.js +2 -1
- nautobot/tenancy/api/serializers.py +0 -2
- nautobot/tenancy/api/views.py +9 -13
- nautobot/tenancy/factory.py +1 -1
- nautobot/tenancy/navigation.py +0 -29
- nautobot/tenancy/templates/tenancy/tenant.html +4 -91
- nautobot/tenancy/tests/test_filters.py +29 -134
- nautobot/tenancy/views.py +35 -24
- nautobot/users/admin.py +3 -1
- nautobot/users/api/serializers.py +4 -4
- nautobot/users/api/views.py +3 -3
- nautobot/users/forms.py +19 -0
- nautobot/users/templates/users/preferences.html +22 -0
- nautobot/users/tests/test_filters.py +1 -19
- nautobot/users/tests/test_views.py +57 -0
- nautobot/users/utils.py +8 -0
- nautobot/users/views.py +48 -11
- nautobot/virtualization/api/serializers.py +4 -4
- nautobot/virtualization/api/views.py +5 -24
- nautobot/virtualization/filters.py +1 -2
- nautobot/virtualization/models.py +1 -1
- nautobot/virtualization/navigation.py +0 -48
- nautobot/virtualization/tables.py +2 -2
- nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -39
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -9
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
- nautobot/virtualization/tests/test_filters.py +57 -166
- nautobot/virtualization/views.py +18 -15
- nautobot/wireless/__init__.py +0 -0
- nautobot/wireless/api/__init__.py +0 -0
- nautobot/wireless/api/serializers.py +44 -0
- nautobot/wireless/api/urls.py +20 -0
- nautobot/wireless/api/views.py +34 -0
- nautobot/wireless/apps.py +8 -0
- nautobot/wireless/choices.py +345 -0
- nautobot/wireless/factory.py +138 -0
- nautobot/wireless/filters.py +167 -0
- nautobot/wireless/forms.py +283 -0
- nautobot/wireless/homepage.py +19 -0
- nautobot/wireless/migrations/0001_initial.py +223 -0
- nautobot/wireless/migrations/__init__.py +0 -0
- nautobot/wireless/models.py +207 -0
- nautobot/wireless/navigation.py +105 -0
- nautobot/wireless/tables.py +244 -0
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +81 -0
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +26 -0
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +88 -0
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +56 -0
- nautobot/wireless/tests/__init__.py +0 -0
- nautobot/wireless/tests/integration/__init__.py +0 -0
- nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
- nautobot/wireless/tests/test_api.py +247 -0
- nautobot/wireless/tests/test_filters.py +82 -0
- nautobot/wireless/tests/test_models.py +22 -0
- nautobot/wireless/tests/test_views.py +378 -0
- nautobot/wireless/urls.py +13 -0
- nautobot/wireless/views.py +119 -0
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/METADATA +11 -13
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/RECORD +783 -613
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/WHEEL +1 -1
- nautobot/core/fixtures/user-data.json +0 -59
- nautobot/core/utils/navigation.py +0 -54
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/NOTICE +0 -0
- {nautobot-2.3.15b1.dist-info → nautobot-2.4.0.dist-info}/entry_points.txt +0 -0
nautobot/core/testing/api.py
CHANGED
|
@@ -5,18 +5,19 @@ from typing import Optional, Sequence, Union
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
7
7
|
from django.contrib.contenttypes.models import ContentType
|
|
8
|
+
from django.db import connections, DEFAULT_DB_ALIAS
|
|
8
9
|
from django.db.models import ForeignKey, ManyToManyField, QuerySet
|
|
9
10
|
from django.test import override_settings, tag
|
|
11
|
+
from django.test.utils import CaptureQueriesContext
|
|
10
12
|
from django.urls import reverse
|
|
11
13
|
from django.utils.text import slugify
|
|
12
|
-
from rest_framework import status
|
|
14
|
+
from rest_framework import serializers, status
|
|
13
15
|
from rest_framework.relations import ManyRelatedField
|
|
14
16
|
from rest_framework.test import APITransactionTestCase as _APITransactionTestCase
|
|
15
17
|
|
|
16
18
|
from nautobot.core.api.utils import get_serializer_for_model
|
|
17
19
|
from nautobot.core.models import fields as core_fields
|
|
18
20
|
from nautobot.core.models.tree_queries import TreeModel
|
|
19
|
-
from nautobot.core.templatetags.helpers import bettertitle
|
|
20
21
|
from nautobot.core.testing import mixins, utils, views
|
|
21
22
|
from nautobot.core.utils import lookup
|
|
22
23
|
from nautobot.core.utils.data import is_uuid
|
|
@@ -95,6 +96,15 @@ class APITestCase(views.ModelTestCase):
|
|
|
95
96
|
for verboten in self.VERBOTEN_STRINGS:
|
|
96
97
|
self.assertNotIn(verboten, response_raw_content)
|
|
97
98
|
|
|
99
|
+
def get_m2m_fields(self):
|
|
100
|
+
"""Get serializer field names that are many-to-many or one-to-many and thus affected by ?exclude_m2m=true."""
|
|
101
|
+
serializer_class = get_serializer_for_model(self.model)
|
|
102
|
+
m2m_fields = []
|
|
103
|
+
for field_name, field_instance in serializer_class().fields.items():
|
|
104
|
+
if isinstance(field_instance, (serializers.ManyRelatedField, serializers.ListSerializer)):
|
|
105
|
+
m2m_fields.append(field_name)
|
|
106
|
+
return m2m_fields
|
|
107
|
+
|
|
98
108
|
|
|
99
109
|
@tag("unit")
|
|
100
110
|
class APIViewTestCases:
|
|
@@ -169,6 +179,10 @@ class APIViewTestCases:
|
|
|
169
179
|
if issubclass(self.model, extras_models.ChangeLoggedModel):
|
|
170
180
|
self.assertIn("created", response.data)
|
|
171
181
|
self.assertIn("last_updated", response.data)
|
|
182
|
+
if hasattr(self.model, "notes") and isinstance(instance1.notes, QuerySet):
|
|
183
|
+
self.assertIn("notes_url", response.data)
|
|
184
|
+
self.assertIn(f"{url}notes/", str(response.data["notes_url"]))
|
|
185
|
+
self.assertIn(instance1.get_notes_url(api=True), str(response.data["notes_url"]))
|
|
172
186
|
# Fields that should be absent by default (opt-in fields):
|
|
173
187
|
self.assertNotIn("computed_fields", response.data)
|
|
174
188
|
self.assertNotIn("relationships", response.data)
|
|
@@ -218,58 +232,6 @@ class APIViewTestCases:
|
|
|
218
232
|
response = self.client.options(url, **self.header)
|
|
219
233
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
220
234
|
|
|
221
|
-
with self.subTest("Assert Detail View Config is generated well"):
|
|
222
|
-
# Namings Help
|
|
223
|
-
# 1. detail_view_config: This is the detail view config set in the serializer.Meta.detail_view_config
|
|
224
|
-
# 2. detail_view_schema: This is the retrieve schema generated from an OPTIONS request.
|
|
225
|
-
# 3. advanced_view_schema: This is the advanced tab schema generated from an OPTIONS request.
|
|
226
|
-
serializer = get_serializer_for_model(self._get_queryset().model)
|
|
227
|
-
advanced_view_schema = response.data["view_options"]["retrieve"]["tabs"]["Advanced"]
|
|
228
|
-
|
|
229
|
-
# Get default advanced tab fields
|
|
230
|
-
self.assertEqual(len(advanced_view_schema), 1)
|
|
231
|
-
self.assertIn("Object Details", advanced_view_schema[0])
|
|
232
|
-
advanced_tab_fields = advanced_view_schema[0].get("Object Details")["fields"]
|
|
233
|
-
|
|
234
|
-
if detail_view_config := getattr(serializer.Meta, "detail_view_config", None):
|
|
235
|
-
detail_view_schema = response.data["view_options"]["retrieve"]["tabs"][
|
|
236
|
-
bettertitle(self._get_queryset().model._meta.verbose_name)
|
|
237
|
-
]
|
|
238
|
-
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
239
|
-
|
|
240
|
-
# According to convention, fields in the advanced tab fields should not exist in
|
|
241
|
-
# the `detail_view_schema`. Assert this is True.
|
|
242
|
-
with self.subTest("Assert advanced tab fields should not exist in the detail_view_schema."):
|
|
243
|
-
if detail_view_config.get("include_others"):
|
|
244
|
-
# Handle "Other Fields" section specially as "Other Field" is dynamically added
|
|
245
|
-
# by Nautobot and is not part of the serializer-defined detail_view_config
|
|
246
|
-
other_fields = detail_view_schema[0]["Other Fields"]["fields"]
|
|
247
|
-
for field in advanced_tab_fields:
|
|
248
|
-
self.assertNotIn(field, other_fields)
|
|
249
|
-
|
|
250
|
-
for col_idx, col in enumerate(detail_view_schema):
|
|
251
|
-
for group_title, group in col.items():
|
|
252
|
-
if group_title == "Other Fields":
|
|
253
|
-
continue
|
|
254
|
-
group_fields = group["fields"]
|
|
255
|
-
# Config on the serializer
|
|
256
|
-
if (
|
|
257
|
-
col_idx < len(detail_view_config["layout"])
|
|
258
|
-
and group_title in detail_view_config["layout"][col_idx]
|
|
259
|
-
):
|
|
260
|
-
fields = detail_view_config["layout"][col_idx][group_title]["fields"]
|
|
261
|
-
else:
|
|
262
|
-
fields = []
|
|
263
|
-
|
|
264
|
-
# Fields that are in the detail_view_schema must not be in the advanced tab as well
|
|
265
|
-
for field in group_fields:
|
|
266
|
-
self.assertNotIn(field, advanced_tab_fields)
|
|
267
|
-
|
|
268
|
-
# Fields that are explicit in the detail_view_config must remain as such in the schema
|
|
269
|
-
for field in fields:
|
|
270
|
-
if field not in advanced_tab_fields:
|
|
271
|
-
self.assertIn(field, group_fields)
|
|
272
|
-
|
|
273
235
|
class ListObjectsViewTestCase(APITestCase):
|
|
274
236
|
choices_fields = None
|
|
275
237
|
filterset = None
|
|
@@ -280,7 +242,7 @@ class APIViewTestCases:
|
|
|
280
242
|
def get_depth_fields(self):
|
|
281
243
|
"""Get a list of model fields that could be tested with the ?depth query parameter"""
|
|
282
244
|
depth_fields = []
|
|
283
|
-
for field in self.model._meta.
|
|
245
|
+
for field in self.model._meta.get_fields():
|
|
284
246
|
if not field.name.startswith("_"):
|
|
285
247
|
if isinstance(field, (ForeignKey, GenericForeignKey, ManyToManyField, core_fields.TagsField)) and (
|
|
286
248
|
# we represent content-types as "app_label.modelname" rather than as FKs
|
|
@@ -289,6 +251,9 @@ class APIViewTestCases:
|
|
|
289
251
|
and not (field.name == "user" and self.model == users_models.Token)
|
|
290
252
|
):
|
|
291
253
|
depth_fields.append(field.name)
|
|
254
|
+
serializer_class = get_serializer_for_model(self.model)
|
|
255
|
+
serializer = serializer_class()
|
|
256
|
+
depth_fields = [field_name for field_name in depth_fields if field_name in serializer.fields]
|
|
292
257
|
return depth_fields
|
|
293
258
|
|
|
294
259
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
@@ -318,9 +283,12 @@ class APIViewTestCases:
|
|
|
318
283
|
GET a list of objects using the "?depth=0" parameter.
|
|
319
284
|
"""
|
|
320
285
|
depth_fields = self.get_depth_fields()
|
|
286
|
+
m2m_fields = self.get_m2m_fields()
|
|
321
287
|
self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
|
|
322
|
-
|
|
323
|
-
|
|
288
|
+
list_url = f"{self._get_list_url()}?depth=0"
|
|
289
|
+
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
290
|
+
response = self.client.get(list_url, **self.header)
|
|
291
|
+
base_num_queries = len(cqc)
|
|
324
292
|
|
|
325
293
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
326
294
|
self.assertIsInstance(response.data, dict)
|
|
@@ -329,15 +297,23 @@ class APIViewTestCases:
|
|
|
329
297
|
self.assert_no_verboten_content(response)
|
|
330
298
|
|
|
331
299
|
for response_data in response.data["results"]:
|
|
300
|
+
for field in m2m_fields:
|
|
301
|
+
self.assertIn(field, response_data)
|
|
302
|
+
self.assertIsInstance(response_data[field], list)
|
|
332
303
|
for field in depth_fields:
|
|
333
304
|
self.assertIn(field, response_data)
|
|
334
305
|
if isinstance(response_data[field], list):
|
|
335
306
|
for entry in response_data[field]:
|
|
336
307
|
self.assertIsInstance(entry, dict)
|
|
337
|
-
|
|
308
|
+
if entry["object_type"] in ["auth.group"]:
|
|
309
|
+
self.assertIsInstance(entry["id"], int)
|
|
310
|
+
else:
|
|
311
|
+
self.assertTrue(is_uuid(entry["id"]))
|
|
312
|
+
self.assertEqual(len(entry.keys()), 3) # just id/object_type/url
|
|
338
313
|
else:
|
|
339
314
|
if response_data[field] is not None:
|
|
340
315
|
self.assertIsInstance(response_data[field], dict)
|
|
316
|
+
self.assertEqual(len(response_data[field].keys()), 3) # just id/object_type/url
|
|
341
317
|
url = response_data[field]["url"]
|
|
342
318
|
pk = response_data[field]["id"]
|
|
343
319
|
object_type = response_data[field]["object_type"]
|
|
@@ -345,22 +321,67 @@ class APIViewTestCases:
|
|
|
345
321
|
# URL ending in the UUID of the relevant object:
|
|
346
322
|
# http://nautobot.example.com/api/circuits/providers/<uuid>/
|
|
347
323
|
# ^^^^^^
|
|
348
|
-
|
|
349
|
-
|
|
324
|
+
if object_type in ["auth.group"]:
|
|
325
|
+
self.assertIsInstance(url.split("/")[-2], int)
|
|
326
|
+
self.assertIsInstance(pk, int)
|
|
327
|
+
else:
|
|
328
|
+
self.assertTrue(is_uuid(url.split("/")[-2]))
|
|
329
|
+
self.assertTrue(is_uuid(pk))
|
|
350
330
|
|
|
351
331
|
with self.subTest(f"Assert object_type {object_type} is valid"):
|
|
352
332
|
app_label, model_name = object_type.split(".")
|
|
353
333
|
ContentType.objects.get(app_label=app_label, model=model_name)
|
|
354
334
|
|
|
335
|
+
list_url += "&exclude_m2m=true"
|
|
336
|
+
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
337
|
+
response = self.client.get(list_url, **self.header)
|
|
338
|
+
|
|
339
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
340
|
+
self.assertIsInstance(response.data, dict)
|
|
341
|
+
self.assertIn("results", response.data)
|
|
342
|
+
self.assert_no_verboten_content(response)
|
|
343
|
+
|
|
344
|
+
if m2m_fields:
|
|
345
|
+
if self.model._meta.app_label in [
|
|
346
|
+
"circuits",
|
|
347
|
+
"cloud",
|
|
348
|
+
"dcim",
|
|
349
|
+
"extras",
|
|
350
|
+
"ipam",
|
|
351
|
+
"tenancy",
|
|
352
|
+
"users",
|
|
353
|
+
"virtualization",
|
|
354
|
+
"wireless",
|
|
355
|
+
]:
|
|
356
|
+
self.assertLess(
|
|
357
|
+
len(cqc), base_num_queries, "Number of queries did not decrease with ?exclude_m2m=true"
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
# Less strict check for non-core APIs
|
|
361
|
+
self.assertLessEqual(
|
|
362
|
+
len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true"
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
# No M2M fields to exclude
|
|
366
|
+
self.assertLessEqual(len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true")
|
|
367
|
+
|
|
368
|
+
for response_data in response.data["results"]:
|
|
369
|
+
for field in m2m_fields:
|
|
370
|
+
self.assertNotIn(field, response_data)
|
|
371
|
+
# TODO: we should assert that all other fields are still present, but there's a few corner cases...
|
|
372
|
+
|
|
355
373
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
356
374
|
def test_list_objects_depth_1(self):
|
|
357
375
|
"""
|
|
358
376
|
GET a list of objects using the "?depth=1" parameter.
|
|
359
377
|
"""
|
|
360
378
|
depth_fields = self.get_depth_fields()
|
|
379
|
+
m2m_fields = self.get_m2m_fields()
|
|
361
380
|
self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
|
|
362
|
-
|
|
363
|
-
|
|
381
|
+
list_url = f"{self._get_list_url()}?depth=1"
|
|
382
|
+
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
383
|
+
response = self.client.get(list_url, **self.header)
|
|
384
|
+
base_num_queries = len(cqc)
|
|
364
385
|
|
|
365
386
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
366
387
|
self.assertIsInstance(response.data, dict)
|
|
@@ -369,16 +390,65 @@ class APIViewTestCases:
|
|
|
369
390
|
self.assert_no_verboten_content(response)
|
|
370
391
|
|
|
371
392
|
for response_data in response.data["results"]:
|
|
393
|
+
for field in m2m_fields:
|
|
394
|
+
self.assertIn(field, response_data)
|
|
395
|
+
self.assertIsInstance(response_data[field], list)
|
|
372
396
|
for field in depth_fields:
|
|
373
397
|
self.assertIn(field, response_data)
|
|
374
398
|
if isinstance(response_data[field], list):
|
|
375
399
|
for entry in response_data[field]:
|
|
376
400
|
self.assertIsInstance(entry, dict)
|
|
377
|
-
|
|
401
|
+
if entry["object_type"] in ["auth.group"]:
|
|
402
|
+
self.assertIsInstance(entry["id"], int)
|
|
403
|
+
else:
|
|
404
|
+
self.assertTrue(is_uuid(entry["id"]))
|
|
405
|
+
self.assertGreater(len(entry.keys()), 3, entry) # not just id/object_type/url!
|
|
378
406
|
else:
|
|
379
407
|
if response_data[field] is not None:
|
|
380
408
|
self.assertIsInstance(response_data[field], dict)
|
|
381
|
-
|
|
409
|
+
if response_data[field]["object_type"] in ["auth.group"]:
|
|
410
|
+
self.assertIsInstance(response_data[field]["id"], int)
|
|
411
|
+
else:
|
|
412
|
+
self.assertTrue(is_uuid(response_data[field]["id"]))
|
|
413
|
+
self.assertGreater(len(response_data[field].keys()), 3, response_data[field])
|
|
414
|
+
|
|
415
|
+
list_url += "&exclude_m2m=true"
|
|
416
|
+
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
417
|
+
response = self.client.get(list_url, **self.header)
|
|
418
|
+
|
|
419
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
420
|
+
self.assertIsInstance(response.data, dict)
|
|
421
|
+
self.assertIn("results", response.data)
|
|
422
|
+
self.assert_no_verboten_content(response)
|
|
423
|
+
|
|
424
|
+
if m2m_fields:
|
|
425
|
+
if self.model._meta.app_label in [
|
|
426
|
+
"circuits",
|
|
427
|
+
"cloud",
|
|
428
|
+
"dcim",
|
|
429
|
+
"extras",
|
|
430
|
+
"ipam",
|
|
431
|
+
"tenancy",
|
|
432
|
+
"users",
|
|
433
|
+
"virtualization",
|
|
434
|
+
"wireless",
|
|
435
|
+
]:
|
|
436
|
+
self.assertLess(
|
|
437
|
+
len(cqc), base_num_queries, "Number of queries did not decrease with ?exclude_m2m=true"
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
# Less strict check for non-core APIs
|
|
441
|
+
self.assertLessEqual(
|
|
442
|
+
len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true"
|
|
443
|
+
)
|
|
444
|
+
else:
|
|
445
|
+
# No M2M fields to exclude
|
|
446
|
+
self.assertLessEqual(len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true")
|
|
447
|
+
|
|
448
|
+
for response_data in response.data["results"]:
|
|
449
|
+
for field in m2m_fields:
|
|
450
|
+
self.assertNotIn(field, response_data)
|
|
451
|
+
# TODO: we should assert that all other fields are still present, but there's a few corner cases...
|
|
382
452
|
|
|
383
453
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
384
454
|
def test_list_objects_without_permission(self):
|
|
@@ -637,10 +707,7 @@ class APIViewTestCases:
|
|
|
637
707
|
POST a single object with permission.
|
|
638
708
|
"""
|
|
639
709
|
# Add object-level permission
|
|
640
|
-
|
|
641
|
-
obj_perm.save()
|
|
642
|
-
obj_perm.users.add(self.user)
|
|
643
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
710
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
644
711
|
|
|
645
712
|
initial_count = self._get_queryset().count()
|
|
646
713
|
for i, create_data in enumerate(self.create_data):
|
|
@@ -683,10 +750,10 @@ class APIViewTestCases:
|
|
|
683
750
|
self.fail("Couldn't find a single deletable object!")
|
|
684
751
|
|
|
685
752
|
# Add object-level permission
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
753
|
+
self.add_permissions(
|
|
754
|
+
f"{self.model._meta.app_label}.add_{self.model._meta.model_name}",
|
|
755
|
+
f"{self.model._meta.app_label}.view_{self.model._meta.model_name}",
|
|
756
|
+
)
|
|
690
757
|
|
|
691
758
|
response = self.client.get(self._get_detail_url(instance) + "?format=csv", **self.header)
|
|
692
759
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
@@ -739,10 +806,7 @@ class APIViewTestCases:
|
|
|
739
806
|
POST a set of objects in a single request.
|
|
740
807
|
"""
|
|
741
808
|
# Add object-level permission
|
|
742
|
-
|
|
743
|
-
obj_perm.save()
|
|
744
|
-
obj_perm.users.add(self.user)
|
|
745
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
809
|
+
self.add_permissions(f"{self.model._meta.app_label}.add_{self.model._meta.model_name}")
|
|
746
810
|
|
|
747
811
|
initial_count = self._get_queryset().count()
|
|
748
812
|
response = self.client.post(self._get_list_url(), self.create_data, format="json", **self.header)
|
|
@@ -796,7 +860,7 @@ class APIViewTestCases:
|
|
|
796
860
|
|
|
797
861
|
def strip_serialized_object(this_object):
|
|
798
862
|
"""
|
|
799
|
-
|
|
863
|
+
Work around acceptable differences in PATCH response vs GET response which are known behaviors.
|
|
800
864
|
"""
|
|
801
865
|
# Work around for https://github.com/nautobot/nautobot/issues/3321
|
|
802
866
|
this_object.pop("last_updated", None)
|
|
@@ -805,6 +869,12 @@ class APIViewTestCases:
|
|
|
805
869
|
this_object.pop("config_context", None)
|
|
806
870
|
this_object.pop("relationships", None)
|
|
807
871
|
|
|
872
|
+
serializer = get_serializer_for_model(self.model)()
|
|
873
|
+
for field_name, field_instance in serializer.fields.items():
|
|
874
|
+
if field_instance.read_only:
|
|
875
|
+
# Likely a derived field, might change as a consequence of other data updates
|
|
876
|
+
this_object.pop(field_name, None)
|
|
877
|
+
|
|
808
878
|
for value in this_object.values():
|
|
809
879
|
if isinstance(value, dict):
|
|
810
880
|
strip_serialized_object(value)
|
|
@@ -848,7 +918,7 @@ class APIViewTestCases:
|
|
|
848
918
|
self.assertEqual(initial_serialized_object, serialized_object)
|
|
849
919
|
|
|
850
920
|
# Verify ObjectChange creation -- yes, even though nothing actually changed
|
|
851
|
-
# This may change (hah) at some point -- see https://github.com/nautobot/nautobot/issues/3321
|
|
921
|
+
# TODO: This may change (hah) at some point -- see https://github.com/nautobot/nautobot/issues/3321
|
|
852
922
|
if hasattr(self.model, "to_objectchange"):
|
|
853
923
|
objectchanges = lookup.get_changes_for_model(instance)
|
|
854
924
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
|
|
@@ -857,10 +927,16 @@ class APIViewTestCases:
|
|
|
857
927
|
# Verify that a PATCH with some data updates that data correctly.
|
|
858
928
|
response = self.client.patch(url, update_data, format="json", **self.header)
|
|
859
929
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
930
|
+
serialized_object = response.json()
|
|
931
|
+
strip_serialized_object(serialized_object)
|
|
860
932
|
# Check for unexpected side effects on fields we DIDN'T intend to update
|
|
861
933
|
for field in initial_serialized_object:
|
|
862
934
|
if field not in update_data:
|
|
863
|
-
self.assertEqual(
|
|
935
|
+
self.assertEqual(
|
|
936
|
+
initial_serialized_object[field],
|
|
937
|
+
serialized_object[field],
|
|
938
|
+
f"data changed unexpectedly for field '{field}'",
|
|
939
|
+
)
|
|
864
940
|
instance.refresh_from_db()
|
|
865
941
|
self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
|
|
866
942
|
|
|
@@ -869,14 +945,34 @@ class APIViewTestCases:
|
|
|
869
945
|
objectchanges = lookup.get_changes_for_model(instance)
|
|
870
946
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
|
|
871
947
|
|
|
948
|
+
# Verify that a PATCH with ?exclude_m2m=true correctly excludes many-to-many fields from the response
|
|
949
|
+
# This also doubles as a test for idempotence of the PATCH request.
|
|
950
|
+
response = self.client.patch(url + "?exclude_m2m=true", update_data, format="json", **self.header)
|
|
951
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
952
|
+
m2m_fields = self.get_m2m_fields()
|
|
953
|
+
serialized_object = response.json()
|
|
954
|
+
strip_serialized_object(serialized_object)
|
|
955
|
+
for field in m2m_fields:
|
|
956
|
+
self.assertNotIn(field, serialized_object)
|
|
957
|
+
# Check for unexpected side effects on fields we DIDN'T intend to update
|
|
958
|
+
for field in initial_serialized_object:
|
|
959
|
+
if field not in update_data and field not in m2m_fields:
|
|
960
|
+
self.assertEqual(
|
|
961
|
+
initial_serialized_object[field],
|
|
962
|
+
serialized_object[field],
|
|
963
|
+
f"data changed unexpectedly for field '{field}'",
|
|
964
|
+
)
|
|
965
|
+
instance.refresh_from_db()
|
|
966
|
+
self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
|
|
967
|
+
|
|
872
968
|
def test_get_put_round_trip(self):
|
|
873
969
|
"""GET and then PUT an object and verify that it's accepted and unchanged."""
|
|
874
970
|
self.maxDiff = None
|
|
875
971
|
# Add object-level permission
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
972
|
+
self.add_permissions(
|
|
973
|
+
f"{self.model._meta.app_label}.view_{self.model._meta.model_name}",
|
|
974
|
+
f"{self.model._meta.app_label}.change_{self.model._meta.model_name}",
|
|
975
|
+
)
|
|
880
976
|
|
|
881
977
|
instance = self._get_queryset().first()
|
|
882
978
|
url = self._get_detail_url(instance)
|
|
@@ -907,10 +1003,7 @@ class APIViewTestCases:
|
|
|
907
1003
|
self.skipTest("Bulk update data not set")
|
|
908
1004
|
|
|
909
1005
|
# Add object-level permission
|
|
910
|
-
|
|
911
|
-
obj_perm.save()
|
|
912
|
-
obj_perm.users.add(self.user)
|
|
913
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
1006
|
+
self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
|
|
914
1007
|
|
|
915
1008
|
id_list = list(self._get_queryset().values_list("id", flat=True)[:3])
|
|
916
1009
|
self.assertEqual(len(id_list), 3, "Insufficient number of objects to test bulk update")
|
|
@@ -954,24 +1047,11 @@ class APIViewTestCases:
|
|
|
954
1047
|
self.assertIn("actions", data)
|
|
955
1048
|
|
|
956
1049
|
# Grab any field that has choices defined (fields with enums)
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
]
|
|
962
|
-
):
|
|
963
|
-
schema = data["schema"]
|
|
964
|
-
props = schema["properties"]
|
|
965
|
-
fields = props.keys()
|
|
966
|
-
field_choices = set()
|
|
967
|
-
for field_name in fields:
|
|
968
|
-
obj = props[field_name]
|
|
969
|
-
if "enum" in obj and "enumNames" in obj:
|
|
970
|
-
enum = obj["enum"]
|
|
971
|
-
# Zipping to assert that the enum and the mapping have the same number of items.
|
|
972
|
-
model_field_choices = dict(zip(obj["enumNames"], enum))
|
|
973
|
-
self.assertEqual(len(enum), len(model_field_choices))
|
|
974
|
-
field_choices.add(field_name)
|
|
1050
|
+
field_choices = {}
|
|
1051
|
+
if "POST" in data["actions"]:
|
|
1052
|
+
field_choices = {k for k, v in data["actions"]["POST"].items() if "choices" in v}
|
|
1053
|
+
elif "PUT" in data["actions"]:
|
|
1054
|
+
field_choices = {k for k, v in data["actions"]["PUT"].items() if "choices" in v}
|
|
975
1055
|
else:
|
|
976
1056
|
self.fail(f"Neither PUT nor POST are available actions in: {data['actions']}")
|
|
977
1057
|
|
|
@@ -1026,10 +1106,7 @@ class APIViewTestCases:
|
|
|
1026
1106
|
url = self._get_detail_url(instance)
|
|
1027
1107
|
|
|
1028
1108
|
# Add object-level permission
|
|
1029
|
-
|
|
1030
|
-
obj_perm.save()
|
|
1031
|
-
obj_perm.users.add(self.user)
|
|
1032
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
1109
|
+
self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
|
|
1033
1110
|
|
|
1034
1111
|
response = self.client.delete(url, **self.header)
|
|
1035
1112
|
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
|
@@ -1046,10 +1123,7 @@ class APIViewTestCases:
|
|
|
1046
1123
|
"""
|
|
1047
1124
|
id_list = self.get_deletable_object_pks()
|
|
1048
1125
|
# Add object-level permission
|
|
1049
|
-
|
|
1050
|
-
obj_perm.save()
|
|
1051
|
-
obj_perm.users.add(self.user)
|
|
1052
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
1126
|
+
self.add_permissions(f"{self.model._meta.app_label}.delete_{self.model._meta.model_name}")
|
|
1053
1127
|
|
|
1054
1128
|
data = [{"id": id} for id in id_list]
|
|
1055
1129
|
|
|
@@ -1061,30 +1135,6 @@ class APIViewTestCases:
|
|
|
1061
1135
|
class NotesURLViewTestCase(APITestCase):
|
|
1062
1136
|
"""Validate Notes URL on objects that have the Note model Mixin."""
|
|
1063
1137
|
|
|
1064
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
1065
|
-
def test_notes_url_on_object(self):
|
|
1066
|
-
if not hasattr(self.model, "notes"):
|
|
1067
|
-
self.skipTest("Model doesn't appear to support Notes")
|
|
1068
|
-
instance = self._get_queryset().first()
|
|
1069
|
-
if not isinstance(instance.notes, QuerySet):
|
|
1070
|
-
self.skipTest("Model has a notes field but it doesn't appear to be Notes")
|
|
1071
|
-
|
|
1072
|
-
# Add object-level permission
|
|
1073
|
-
obj_perm = users_models.ObjectPermission(
|
|
1074
|
-
name="Test permission",
|
|
1075
|
-
constraints={"pk": instance.pk},
|
|
1076
|
-
actions=["view"],
|
|
1077
|
-
)
|
|
1078
|
-
obj_perm.save()
|
|
1079
|
-
obj_perm.users.add(self.user)
|
|
1080
|
-
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
1081
|
-
url = self._get_detail_url(instance)
|
|
1082
|
-
response = self.client.get(url, **self.header)
|
|
1083
|
-
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1084
|
-
self.assertIn("notes_url", response.data)
|
|
1085
|
-
self.assertIn(f"{url}notes/", str(response.data["notes_url"]))
|
|
1086
|
-
self.assertIn(instance.get_notes_url(api=True), str(response.data["notes_url"]))
|
|
1087
|
-
|
|
1088
1138
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
1089
1139
|
def test_notes_url_functionality(self):
|
|
1090
1140
|
if not hasattr(self.model, "notes"):
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from django.test.utils import TestContextDecorator
|
|
2
|
+
|
|
3
|
+
from nautobot.core.events import _EVENT_BROKERS, deregister_event_broker, load_event_brokers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class load_event_broker_override_settings(TestContextDecorator):
|
|
7
|
+
def __init__(self, **kwargs) -> None:
|
|
8
|
+
self.options = kwargs
|
|
9
|
+
super().__init__()
|
|
10
|
+
|
|
11
|
+
def enable(self):
|
|
12
|
+
"""Registered event brokers"""
|
|
13
|
+
load_event_brokers(self.options["EVENT_BROKERS"])
|
|
14
|
+
|
|
15
|
+
def disable(self):
|
|
16
|
+
"""Clear all registered event brokers"""
|
|
17
|
+
for event_broker in _EVENT_BROKERS:
|
|
18
|
+
deregister_event_broker(event_broker)
|