nautobot 2.3.16__py3-none-any.whl → 2.4.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/__init__.py +15 -0
- nautobot/apps/api.py +0 -2
- nautobot/apps/config.py +32 -3
- nautobot/apps/events.py +19 -0
- nautobot/apps/exceptions.py +0 -2
- nautobot/apps/ui.py +44 -9
- nautobot/apps/utils.py +0 -8
- nautobot/apps/views.py +2 -0
- nautobot/circuits/navigation.py +0 -57
- nautobot/circuits/tables.py +1 -2
- 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/factory.py +4 -1
- nautobot/cloud/models.py +1 -1
- nautobot/cloud/tests/test_filters.py +5 -4
- nautobot/core/api/fields.py +5 -5
- nautobot/core/api/metadata.py +28 -256
- nautobot/core/api/pagination.py +3 -2
- nautobot/core/api/renderers.py +3 -0
- nautobot/core/api/serializers.py +24 -244
- nautobot/core/api/urls.py +3 -4
- nautobot/core/api/utils.py +0 -62
- nautobot/core/api/views.py +48 -158
- nautobot/core/apps/__init__.py +22 -578
- nautobot/core/celery/__init__.py +13 -0
- nautobot/core/celery/log.py +4 -4
- nautobot/core/celery/schedulers.py +48 -3
- 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 +16 -21
- nautobot/core/fixtures/user-data.json +59 -0
- nautobot/core/forms/fields.py +53 -8
- 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 +4 -4
- nautobot/core/jobs/cleanup.py +13 -49
- 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 +3 -6
- nautobot/core/settings.py +44 -7
- nautobot/core/settings.yaml +86 -8
- nautobot/core/tables.py +15 -65
- nautobot/core/tasks.py +1 -1
- 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 +0 -3
- nautobot/core/templates/inc/nav_menu.html +1 -1
- nautobot/core/templates/inc/relationships_panel.html +1 -1
- nautobot/core/templates/nautobot_config.py.j2 +3 -3
- nautobot/core/templates/panel_table.html +12 -0
- nautobot/core/templates/search.html +0 -7
- nautobot/core/templates/utilities/render_jinja2.html +117 -0
- nautobot/core/templatetags/helpers.py +101 -12
- nautobot/core/templatetags/ui_framework.py +40 -0
- nautobot/core/testing/api.py +23 -128
- nautobot/core/testing/context.py +18 -0
- nautobot/core/testing/filters.py +41 -58
- nautobot/core/testing/mixins.py +2 -7
- nautobot/core/testing/views.py +25 -123
- nautobot/core/tests/integration/test_app_home.py +1 -0
- nautobot/core/tests/integration/test_app_navbar.py +1 -0
- nautobot/core/tests/integration/test_filters.py +2 -0
- nautobot/core/tests/integration/test_home.py +1 -0
- nautobot/core/tests/integration/test_navbar.py +1 -0
- nautobot/core/tests/integration/test_view_authentication.py +1 -2
- nautobot/core/tests/nautobot_config.py +198 -0
- nautobot/core/tests/runner.py +3 -3
- nautobot/core/tests/test_api.py +82 -201
- nautobot/core/tests/test_csv.py +3 -25
- nautobot/core/tests/test_events.py +214 -0
- nautobot/core/tests/test_jinja_filters.py +1 -0
- nautobot/core/tests/test_jobs.py +84 -13
- nautobot/core/tests/test_navigations.py +7 -241
- nautobot/core/tests/test_templatetags_helpers.py +16 -0
- nautobot/core/tests/test_ui.py +150 -0
- nautobot/core/tests/test_utils.py +0 -25
- nautobot/core/tests/test_views.py +123 -31
- 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 +279 -0
- nautobot/core/ui/object_detail.py +1841 -0
- nautobot/core/ui/utils.py +36 -0
- nautobot/core/urls.py +4 -9
- nautobot/core/utils/config.py +30 -3
- nautobot/core/utils/lookup.py +20 -13
- nautobot/core/views/__init__.py +6 -1
- nautobot/core/views/generic.py +47 -52
- nautobot/core/views/mixins.py +15 -25
- nautobot/core/views/paginator.py +8 -5
- nautobot/core/views/renderers.py +3 -3
- nautobot/core/views/utils.py +11 -0
- nautobot/core/wsgi.py +3 -3
- nautobot/dcim/api/serializers.py +80 -179
- nautobot/dcim/api/urls.py +5 -0
- nautobot/dcim/api/views.py +17 -4
- nautobot/dcim/apps.py +1 -0
- nautobot/dcim/choices.py +28 -0
- nautobot/dcim/factory.py +58 -0
- nautobot/dcim/filters/__init__.py +197 -24
- nautobot/dcim/forms.py +203 -12
- 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/models/__init__.py +4 -0
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +20 -22
- nautobot/dcim/models/devices.py +173 -4
- nautobot/dcim/models/locations.py +3 -3
- nautobot/dcim/models/power.py +5 -6
- 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 +96 -2
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/templates/dcim/controller/base.html +10 -0
- nautobot/dcim/templates/dcim/controller_create.html +1 -0
- nautobot/dcim/templates/dcim/controller_retrieve.html +5 -1
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +25 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +66 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +46 -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 +3 -1
- nautobot/dcim/templates/dcim/interface.html +1 -0
- nautobot/dcim/templates/dcim/interface_edit.html +1 -0
- nautobot/dcim/templates/dcim/locationtype.html +0 -107
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +8 -0
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +76 -0
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +34 -0
- nautobot/dcim/tests/test_api.py +172 -61
- nautobot/dcim/tests/test_filters.py +171 -109
- nautobot/dcim/tests/test_forms.py +2 -51
- nautobot/dcim/tests/test_graphql.py +0 -52
- nautobot/dcim/tests/test_models.py +126 -4
- nautobot/dcim/tests/test_signals.py +1 -0
- nautobot/dcim/tests/test_views.py +103 -11
- nautobot/dcim/urls.py +72 -27
- nautobot/dcim/utils.py +2 -2
- nautobot/dcim/views.py +369 -62
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +91 -75
- nautobot/extras/api/urls.py +4 -0
- nautobot/extras/api/views.py +78 -15
- nautobot/extras/choices.py +13 -0
- nautobot/extras/constants.py +0 -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 +59 -0
- nautobot/extras/forms/forms.py +125 -30
- nautobot/extras/forms/mixins.py +3 -11
- nautobot/extras/graphql/types.py +25 -1
- nautobot/extras/group_sync.py +3 -3
- nautobot/extras/health_checks.py +2 -1
- nautobot/extras/jobs.py +62 -26
- 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 +1 -3
- nautobot/extras/migrations/0018_joblog_data_migration.py +9 -7
- 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/models/__init__.py +4 -0
- nautobot/extras/models/change_logging.py +7 -3
- nautobot/extras/models/customfields.py +11 -12
- nautobot/extras/models/groups.py +9 -13
- nautobot/extras/models/jobs.py +218 -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 +56 -32
- nautobot/extras/plugins/marketplace_manifest.yml +450 -0
- nautobot/extras/plugins/urls.py +1 -0
- nautobot/extras/plugins/views.py +48 -1
- nautobot/extras/signals.py +39 -1
- nautobot/extras/tables.py +40 -6
- 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 +36 -6
- nautobot/extras/templates/extras/job_edit.html +5 -2
- nautobot/extras/templates/extras/job_list.html +2 -7
- nautobot/extras/templates/extras/jobqueue_retrieve.html +44 -0
- nautobot/extras/templates/extras/marketplace.html +278 -0
- nautobot/extras/templates/extras/plugins_list.html +35 -1
- nautobot/extras/templates/extras/plugins_tiles.html +79 -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 +1 -3
- 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/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 +2 -0
- 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 +1 -2
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_filters.py +219 -535
- nautobot/extras/tests/test_forms.py +1 -20
- nautobot/extras/tests/test_job_variables.py +73 -152
- nautobot/extras/tests/test_jobs.py +43 -54
- nautobot/extras/tests/test_models.py +71 -16
- nautobot/extras/tests/test_relationships.py +5 -2
- nautobot/extras/tests/test_utils.py +23 -2
- nautobot/extras/tests/test_views.py +183 -43
- nautobot/extras/tests/test_webhooks.py +2 -1
- nautobot/extras/urls.py +2 -20
- nautobot/extras/utils.py +118 -4
- nautobot/extras/views.py +203 -92
- nautobot/extras/webhooks.py +5 -2
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +36 -137
- nautobot/ipam/api/views.py +93 -62
- nautobot/ipam/lookups.py +62 -101
- nautobot/ipam/models.py +11 -63
- nautobot/ipam/navigation.py +0 -90
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tables.py +6 -20
- nautobot/ipam/templates/ipam/routetarget.html +0 -28
- nautobot/ipam/templates/ipam/vrf.html +0 -47
- nautobot/ipam/tests/test_api.py +8 -419
- nautobot/ipam/tests/test_filters.py +39 -119
- nautobot/ipam/tests/test_forms.py +47 -51
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_models.py +0 -41
- nautobot/ipam/tests/test_querysets.py +1 -63
- nautobot/ipam/urls.py +3 -69
- nautobot/ipam/utils/__init__.py +0 -24
- nautobot/ipam/views.py +153 -198
- nautobot/project-static/css/base.css +38 -3
- nautobot/project-static/docs/404.html +421 -19
- nautobot/project-static/docs/apps/index.html +421 -19
- nautobot/project-static/docs/apps/nautobot-apps.html +421 -19
- nautobot/project-static/docs/assets/extra.css +5 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.83f73b43.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.83f73b43.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +421 -172
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +425 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +424 -22
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9809 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +424 -63
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +457 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +425 -25
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +457 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +425 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +430 -342
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5799 -1054
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +421 -19
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +447 -176
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +460 -21
- nautobot/project-static/docs/development/apps/api/configuration-view.html +421 -19
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +421 -19
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +421 -19
- nautobot/project-static/docs/development/apps/api/models/global-search.html +421 -19
- nautobot/project-static/docs/development/apps/api/models/graphql.html +421 -19
- nautobot/project-static/docs/development/apps/api/models/index.html +421 -19
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +421 -19
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +424 -41
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +421 -19
- nautobot/project-static/docs/development/apps/api/prometheus.html +421 -19
- nautobot/project-static/docs/development/apps/api/setup.html +425 -155
- nautobot/project-static/docs/development/apps/api/testing.html +421 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +421 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +421 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +421 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +421 -19
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +701 -130
- nautobot/project-static/docs/development/apps/api/views/base-template.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/index.html +423 -20
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +425 -19
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +451 -19
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/notes.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +421 -19
- nautobot/project-static/docs/development/apps/api/views/urls.html +421 -19
- nautobot/project-static/docs/development/apps/index.html +421 -19
- nautobot/project-static/docs/development/apps/migration/code-updates.html +422 -52
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +422 -20
- nautobot/project-static/docs/development/apps/migration/from-v1.html +421 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +421 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +421 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +421 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +424 -22
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9219 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +9333 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +9474 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +9517 -0
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +424 -22
- nautobot/project-static/docs/development/core/application-registry.html +421 -19
- nautobot/project-static/docs/development/core/best-practices.html +421 -19
- nautobot/project-static/docs/development/core/bootstrap-ui.html +421 -19
- nautobot/project-static/docs/development/core/caching.html +421 -19
- nautobot/project-static/docs/development/core/controllers.html +423 -19
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +490 -45
- nautobot/project-static/docs/development/core/generic-views.html +421 -19
- nautobot/project-static/docs/development/core/getting-started.html +566 -179
- nautobot/project-static/docs/development/core/homepage.html +432 -30
- nautobot/project-static/docs/development/core/index.html +421 -19
- nautobot/project-static/docs/development/core/local-k8s.html +9453 -0
- nautobot/project-static/docs/development/core/model-checklist.html +424 -22
- nautobot/project-static/docs/development/core/model-features.html +421 -19
- nautobot/project-static/docs/development/core/natural-keys.html +421 -19
- nautobot/project-static/docs/development/core/navigation-menu.html +438 -26
- nautobot/project-static/docs/development/core/release-checklist.html +435 -45
- nautobot/project-static/docs/development/core/role-internals.html +421 -19
- nautobot/project-static/docs/development/core/settings.html +421 -19
- nautobot/project-static/docs/development/core/style-guide.html +421 -19
- nautobot/project-static/docs/development/core/templates.html +431 -22
- nautobot/project-static/docs/development/core/testing.html +421 -19
- nautobot/project-static/docs/development/core/ui-component-framework.html +11020 -0
- nautobot/project-static/docs/development/core/user-preferences.html +424 -22
- nautobot/project-static/docs/development/index.html +421 -19
- nautobot/project-static/docs/development/jobs/index.html +546 -160
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +421 -19
- nautobot/project-static/docs/index.html +421 -19
- 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/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/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 +426 -20
- nautobot/project-static/docs/overview/design_philosophy.html +421 -19
- nautobot/project-static/docs/release-notes/index.html +445 -22
- nautobot/project-static/docs/release-notes/version-1.0.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.1.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.2.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.3.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.4.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.5.html +421 -19
- nautobot/project-static/docs/release-notes/version-1.6.html +634 -667
- nautobot/project-static/docs/release-notes/version-2.0.html +421 -19
- nautobot/project-static/docs/release-notes/version-2.1.html +421 -19
- nautobot/project-static/docs/release-notes/version-2.2.html +421 -19
- nautobot/project-static/docs/release-notes/version-2.3.html +684 -886
- nautobot/project-static/docs/release-notes/version-2.4.html +10007 -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 +334 -270
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +421 -19
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +421 -19
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +423 -21
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +433 -32
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +421 -19
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +765 -180
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +434 -29
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +421 -19
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/index.html +426 -20
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +421 -19
- nautobot/project-static/docs/user-guide/administration/installation/services.html +421 -19
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +421 -19
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +442 -41
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +435 -66
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +435 -66
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +421 -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 +421 -19
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +427 -21
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +457 -20
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +447 -22
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9333 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +424 -22
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +421 -19
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +424 -22
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9271 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9175 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9169 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9235 -0
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +421 -19
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +421 -19
- nautobot/project-static/docs/user-guide/index.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +9575 -0
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +426 -20
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9182 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9250 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +424 -22
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +489 -56
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +421 -19
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +421 -19
- nautobot/project-static/img/jinja_logo.svg +97 -0
- nautobot/project-static/js/forms.js +5 -0
- nautobot/project-static/js/nav_menu.js +2 -1
- nautobot/tenancy/api/serializers.py +0 -2
- 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 +32 -23
- nautobot/users/admin.py +3 -1
- nautobot/users/api/serializers.py +4 -5
- nautobot/users/api/views.py +1 -1
- 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/filters.py +2 -20
- nautobot/virtualization/navigation.py +0 -48
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -39
- nautobot/virtualization/tests/test_filters.py +57 -183
- 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 +64 -0
- nautobot/wireless/tests/__init__.py +0 -0
- nautobot/wireless/tests/test_api.py +247 -0
- nautobot/wireless/tests/test_filters.py +54 -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 +129 -0
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/METADATA +11 -14
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/RECORD +674 -551
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/WHEEL +1 -1
- nautobot/core/utils/navigation.py +0 -54
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/NOTICE +0 -0
- {nautobot-2.3.16.dist-info → nautobot-2.4.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
from collections.abc import Mapping
|
|
2
2
|
from datetime import datetime, timedelta
|
|
3
|
+
import json
|
|
3
4
|
import logging
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
import sys
|
|
5
7
|
|
|
6
8
|
from celery import current_app
|
|
9
|
+
from celery.beat import _evaluate_entry_args, _evaluate_entry_kwargs, reraise, SchedulingError
|
|
10
|
+
from celery.result import AsyncResult
|
|
7
11
|
from django.conf import settings
|
|
8
12
|
from django_celery_beat.schedulers import DatabaseScheduler, ModelEntry
|
|
9
13
|
from kombu.utils.json import loads
|
|
10
14
|
|
|
11
|
-
from nautobot.extras.
|
|
15
|
+
from nautobot.extras.choices import JobQueueTypeChoices
|
|
16
|
+
from nautobot.extras.models import JobResult, ScheduledJob, ScheduledJobs
|
|
17
|
+
from nautobot.extras.utils import run_kubernetes_job_and_return_job_result
|
|
12
18
|
|
|
13
19
|
logger = logging.getLogger(__name__)
|
|
14
20
|
|
|
@@ -19,7 +25,7 @@ class NautobotScheduleEntry(ModelEntry):
|
|
|
19
25
|
nautobot.extras.models.ScheduledJob model
|
|
20
26
|
"""
|
|
21
27
|
|
|
22
|
-
def __init__(self, model, app=None):
|
|
28
|
+
def __init__(self, model, app=None):
|
|
23
29
|
"""Initialize the model entry."""
|
|
24
30
|
# copy-paste from django_celery_beat.schedulers
|
|
25
31
|
self.app = app or current_app._get_current_object()
|
|
@@ -111,7 +117,46 @@ class NautobotDatabaseScheduler(DatabaseScheduler):
|
|
|
111
117
|
|
|
112
118
|
Ref: https://github.com/celery/django-celery-beat/issues/558#issuecomment-1162730008
|
|
113
119
|
"""
|
|
114
|
-
resp =
|
|
120
|
+
resp = None
|
|
121
|
+
entry = self.reserve(entry) if advance else entry
|
|
122
|
+
task = self.app.tasks.get(entry.task)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
entry_args = _evaluate_entry_args(entry.args)
|
|
126
|
+
entry_kwargs = _evaluate_entry_kwargs(entry.kwargs)
|
|
127
|
+
if task:
|
|
128
|
+
scheduled_job = entry.model
|
|
129
|
+
job_queue = scheduled_job.job_queue
|
|
130
|
+
# Distinguish between Celery and Kubernetes job queues
|
|
131
|
+
if job_queue.queue_type == JobQueueTypeChoices.TYPE_KUBERNETES:
|
|
132
|
+
job_result = JobResult.objects.create(
|
|
133
|
+
name=scheduled_job.job_model.name,
|
|
134
|
+
job_model=scheduled_job.job_model,
|
|
135
|
+
scheduled_job=scheduled_job,
|
|
136
|
+
user=scheduled_job.user,
|
|
137
|
+
task_name=scheduled_job.job_model.class_path,
|
|
138
|
+
celery_kwargs=entry.options,
|
|
139
|
+
)
|
|
140
|
+
job_result = run_kubernetes_job_and_return_job_result(
|
|
141
|
+
job_queue, job_result, json.dumps(entry_kwargs)
|
|
142
|
+
)
|
|
143
|
+
# Return an AsyncResult object to mimic the behavior of Celery tasks after the job is finished by Kubernetes Job Pod.
|
|
144
|
+
resp = AsyncResult(job_result.id)
|
|
145
|
+
else:
|
|
146
|
+
resp = task.apply_async(entry_args, entry_kwargs, producer=producer, **entry.options)
|
|
147
|
+
else:
|
|
148
|
+
resp = self.send_task(entry.task, entry_args, entry_kwargs, producer=producer, **entry.options)
|
|
149
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
150
|
+
reraise(
|
|
151
|
+
SchedulingError,
|
|
152
|
+
SchedulingError(f"Couldn't apply scheduled task {entry.name}: {exc}"),
|
|
153
|
+
sys.exc_info()[2],
|
|
154
|
+
)
|
|
155
|
+
finally:
|
|
156
|
+
self._tasks_since_sync += 1
|
|
157
|
+
if self.should_sync():
|
|
158
|
+
self._do_sync()
|
|
159
|
+
|
|
115
160
|
if entry.total_run_count != entry.model.total_run_count:
|
|
116
161
|
entry.total_run_count = entry.model.total_run_count
|
|
117
162
|
entry.model.save()
|
nautobot/core/cli/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ from django.core.management import CommandError, CommandParser, execute_from_com
|
|
|
13
13
|
from django.core.management.utils import get_random_secret_key
|
|
14
14
|
from jinja2 import BaseLoader, Environment
|
|
15
15
|
|
|
16
|
+
from nautobot.core.events import load_event_brokers
|
|
16
17
|
from nautobot.core.settings_funcs import is_truthy
|
|
17
18
|
from nautobot.extras.plugins.utils import load_plugins
|
|
18
19
|
|
|
@@ -44,6 +45,7 @@ def _preprocess_settings(settings, config_path):
|
|
|
44
45
|
- Set up 'job_logs' database mirror
|
|
45
46
|
- Handle our custom `STORAGE_BACKEND` setting.
|
|
46
47
|
- Load plugins based on settings.PLUGINS (potentially affecting INSTALLED_APPS, MIDDLEWARE, and CONSTANCE_CONFIG)
|
|
48
|
+
- Load event brokers based on settings.EVENT_BROKERS
|
|
47
49
|
"""
|
|
48
50
|
settings.SETTINGS_PATH = config_path
|
|
49
51
|
|
|
@@ -124,6 +126,12 @@ def _preprocess_settings(settings, config_path):
|
|
|
124
126
|
# passed in.
|
|
125
127
|
load_plugins(settings)
|
|
126
128
|
|
|
129
|
+
#
|
|
130
|
+
# Event Broker
|
|
131
|
+
#
|
|
132
|
+
|
|
133
|
+
load_event_brokers(settings.EVENT_BROKERS)
|
|
134
|
+
|
|
127
135
|
|
|
128
136
|
def load_settings(config_path):
|
|
129
137
|
"""Load nautobot_config.py or its equivalent into memory as a `nautobot_config` pseudo-module."""
|
nautobot/core/constants.py
CHANGED
|
@@ -110,6 +110,10 @@ CONFIG_SETTING_SEPARATOR = ","
|
|
|
110
110
|
|
|
111
111
|
CHARFIELD_MAX_LENGTH = 255
|
|
112
112
|
|
|
113
|
+
# Default values for pagination settings.
|
|
114
|
+
MAX_PAGE_SIZE_DEFAULT = 1000
|
|
115
|
+
PAGINATE_COUNT_DEFAULT = 50
|
|
116
|
+
|
|
113
117
|
# Models excluded from the global search list
|
|
114
118
|
GLOBAL_SEARCH_EXCLUDE_LIST = [
|
|
115
119
|
"anotherexamplemodel",
|
|
@@ -146,12 +150,15 @@ GLOBAL_SEARCH_EXCLUDE_LIST = [
|
|
|
146
150
|
"interfaceredundancygroup",
|
|
147
151
|
"interfaceredundancygroupassociation",
|
|
148
152
|
"interfacetemplate",
|
|
153
|
+
"interfacevdcassignment",
|
|
149
154
|
"inventoryitem",
|
|
150
155
|
"ipaddresstointerface",
|
|
151
156
|
"job",
|
|
152
157
|
"jobbutton",
|
|
153
158
|
"jobhook",
|
|
154
159
|
"joblogentry",
|
|
160
|
+
"jobqueue",
|
|
161
|
+
"jobqueueassignment",
|
|
155
162
|
"jobresult",
|
|
156
163
|
"locationtype",
|
|
157
164
|
"manufacturer",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Module providing for the publication of event notifications via mechanisms such as Redis, Kafka, syslog, etc."""
|
|
2
|
+
|
|
3
|
+
import fnmatch
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from django.utils.module_loading import import_string
|
|
8
|
+
|
|
9
|
+
from nautobot.core.celery import NautobotKombuJSONEncoder
|
|
10
|
+
from nautobot.core.events.exceptions import EventBrokerImproperlyConfigured, EventBrokerNotFound
|
|
11
|
+
|
|
12
|
+
from .base import EventBroker
|
|
13
|
+
from .redis_broker import RedisEventBroker
|
|
14
|
+
from .syslog_broker import SyslogEventBroker
|
|
15
|
+
|
|
16
|
+
_EVENT_BROKERS = []
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_event_brokers(event_broker_configs):
|
|
23
|
+
"""Process plugins and log errors if they can't be loaded."""
|
|
24
|
+
for broker_name, broker in event_broker_configs.items():
|
|
25
|
+
options = broker.get("OPTIONS", {})
|
|
26
|
+
topics = broker.get("TOPICS", {})
|
|
27
|
+
if not isinstance(topics, dict):
|
|
28
|
+
raise EventBrokerImproperlyConfigured(
|
|
29
|
+
f"{broker_name} Malformed Event Broker Settings: Expected `TOPICS` to be a 'dict', instead a '{type(topics).__name__}' was provided"
|
|
30
|
+
)
|
|
31
|
+
include_topics = topics.get("INCLUDE")
|
|
32
|
+
if include_topics and not isinstance(include_topics, (list, tuple)):
|
|
33
|
+
raise EventBrokerImproperlyConfigured(
|
|
34
|
+
f"{broker_name} Malformed Event Broker Settings: Expected `INCLUDE` to be a 'list' or 'tuple', instead a '{type(include_topics).__name__}' was provided"
|
|
35
|
+
)
|
|
36
|
+
exclude_topics = topics.get("EXCLUDE", [])
|
|
37
|
+
if exclude_topics and not isinstance(exclude_topics, (list, tuple)):
|
|
38
|
+
raise EventBrokerImproperlyConfigured(
|
|
39
|
+
f"{broker_name} Malformed Event Broker Settings: Expected `EXCLUDE` to be a 'list' or 'tuple', instead a '{type(exclude_topics).__name__}' was provided"
|
|
40
|
+
)
|
|
41
|
+
options.update({"include_topics": include_topics, "exclude_topics": exclude_topics})
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
event_broker_class = import_string(broker["CLASS"])
|
|
45
|
+
if not issubclass(event_broker_class, EventBroker):
|
|
46
|
+
raise EventBrokerImproperlyConfigured(
|
|
47
|
+
f"{broker_name} Malformed Event Broker Settings: Broker provided is not an EventBroker"
|
|
48
|
+
)
|
|
49
|
+
event_broker = event_broker_class(**options)
|
|
50
|
+
register_event_broker(event_broker)
|
|
51
|
+
except ImportError as err:
|
|
52
|
+
raise EventBrokerNotFound(f"Unable to import Event Broker {broker_name}.") from err
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register_event_broker(event_broker):
|
|
56
|
+
"""
|
|
57
|
+
Register an `EventBroker` instance for use by Nautobot.
|
|
58
|
+
|
|
59
|
+
The `publish_event()` API will publish events to each registered broker.
|
|
60
|
+
The expectation/intent here, at least initially, is that a given deployment will instantiate zero or more
|
|
61
|
+
EventBrokers, then call `register_event_broker()` for each one, as part of Nautobot initial startup.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
event_broker (EventBroker): The initialized/configured EventBroker instance to register.
|
|
65
|
+
"""
|
|
66
|
+
if event_broker not in _EVENT_BROKERS:
|
|
67
|
+
_EVENT_BROKERS.append(event_broker)
|
|
68
|
+
logger.debug("Registered %s as an event broker", event_broker)
|
|
69
|
+
else:
|
|
70
|
+
logger.warning("Tried to register event broker %s but it was already registered", event_broker)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def deregister_event_broker(event_broker):
|
|
74
|
+
"""
|
|
75
|
+
Deregister a previously registered `EventBroker` instance so that it no longer receives published events.
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
_EVENT_BROKERS.remove(event_broker)
|
|
79
|
+
logger.debug("Deregistered event broker %s", event_broker)
|
|
80
|
+
except ValueError:
|
|
81
|
+
logger.warning("Tried to deregister event broker %s but it wasn't previously registered", event_broker)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def is_topic_match(topic, patterns):
|
|
85
|
+
return any(fnmatch.fnmatch(topic, pattern) for pattern in patterns)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def publish_event(*, topic, payload):
|
|
89
|
+
"""Publish the given event payload to the given topic via all registered `EventBroker` instances.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
topic (str): Topic identifier.
|
|
93
|
+
Convention is to use snake_case and use periods to delineate related groups of topics,
|
|
94
|
+
similar to Python logger naming. For example, you might use `nautobot.create.dcim.device`,
|
|
95
|
+
`nautobot.update.dcim.device`, `nautobot.delete.dcim.device`, `nautobot.create.dcim.interface`,
|
|
96
|
+
`nautobot.create.ipam.ipaddress`, etc.
|
|
97
|
+
payload (dict): JSON-serializable structured data to publish.
|
|
98
|
+
While not all EventBrokers may actually use JSON as their data format, it makes for a reasonable
|
|
99
|
+
lowest common denominator for serializability.
|
|
100
|
+
"""
|
|
101
|
+
for event_broker in _EVENT_BROKERS:
|
|
102
|
+
exclude_topics = event_broker.exclude_topics
|
|
103
|
+
include_topics = event_broker.include_topics
|
|
104
|
+
if is_topic_match(topic, include_topics) and not is_topic_match(topic, exclude_topics):
|
|
105
|
+
serialized_payload = json.dumps(payload, cls=NautobotKombuJSONEncoder)
|
|
106
|
+
event_broker.publish(topic=topic, payload=serialized_payload)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
__all__ = (
|
|
110
|
+
"deregister_event_broker",
|
|
111
|
+
"EventBroker",
|
|
112
|
+
"publish_event",
|
|
113
|
+
"RedisEventBroker",
|
|
114
|
+
"register_event_broker",
|
|
115
|
+
"SyslogEventBroker",
|
|
116
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Base classes for Nautobot event notification framework."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EventBroker(ABC):
|
|
7
|
+
"""Abstract base class for concrete implementations of event brokers such as syslog, Redis, Kafka, etc."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, *args, include_topics=None, exclude_topics=None, **kwargs) -> None:
|
|
10
|
+
self.include_topics = include_topics or ["*"]
|
|
11
|
+
self.exclude_topics = exclude_topics or []
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def publish(self, *, topic, payload):
|
|
16
|
+
"""
|
|
17
|
+
Possibly publish the given data `payload` to the given event `topic`.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
topic (str): Topic identifier.
|
|
21
|
+
Convention is to use snake_case and use periods to delineate related groups of topics,
|
|
22
|
+
similar to Python logger naming. For example, you might receive topics such as `create.dcim.device`,
|
|
23
|
+
`update.dcim.device`, `delete.dcim.device`, `create.dcim.interface`, `create.ipam.ipaddress`, etc.
|
|
24
|
+
payload (dict): JSON-serializable structured data to publish.
|
|
25
|
+
While not all EventBrokers may actually use JSON as their data format, it makes for a reasonable
|
|
26
|
+
lowest common denominator for serializability.
|
|
27
|
+
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class EventBrokerError(Exception):
|
|
2
|
+
"""Base exception for all event-broker-related errors."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EventBrokerNotFound(EventBrokerError):
|
|
6
|
+
"""Raised when a specified event broker module cannot be found."""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventBrokerImproperlyConfigured(EventBrokerError):
|
|
10
|
+
"""Raised when a event is not properly configured."""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Simple EventBroker for publishing events to Redis.
|
|
2
|
+
|
|
3
|
+
To verify that an instance of this broker is working, you can do the following in `nautobot-server nbshell`:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import redis
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
connection = redis.StrictRedis(host="redis", port=6379, db=2, password="...", charset="utf-8", decode_responses=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def listen():
|
|
15
|
+
sub = connection.pubsub()
|
|
16
|
+
sub.psubscribe("nautobot.*")
|
|
17
|
+
for message in sub.listen():
|
|
18
|
+
if message["type"] == "pmessage":
|
|
19
|
+
print(f"Got a pmessage on channel {message['channel']}")
|
|
20
|
+
print(json.dumps(json.loads(message["data"]), indent=2))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
listen()
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then perform any action that triggers an event, such as creating or updating a record through the Nautobot UI.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import redis
|
|
30
|
+
|
|
31
|
+
from .base import EventBroker
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class RedisEventBroker(EventBroker):
|
|
35
|
+
"""EventBroker for publishing events to Redis."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, *args, url, **kwargs):
|
|
38
|
+
"""Initialize and configure a RedisEventBroker.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
url (str): The Redis URL to connect to.
|
|
42
|
+
"""
|
|
43
|
+
self.url = url
|
|
44
|
+
self.connection = redis.StrictRedis.from_url(self.url)
|
|
45
|
+
super().__init__(*args, **kwargs)
|
|
46
|
+
|
|
47
|
+
def publish(self, *, topic, payload):
|
|
48
|
+
self.connection.publish(topic, payload)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Simple EventBroker that just emits syslogs."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from .base import EventBroker
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SyslogEventBroker(EventBroker):
|
|
10
|
+
def __init__(self, *args, level=logging.INFO, **kwargs):
|
|
11
|
+
"""Initialize a SyslogEventBroker that emits logs at the given level."""
|
|
12
|
+
super().__init__(*args, **kwargs)
|
|
13
|
+
self.level = level
|
|
14
|
+
|
|
15
|
+
def publish(self, *, topic, payload):
|
|
16
|
+
logger = logging.getLogger(f"nautobot.events.{topic}")
|
|
17
|
+
# Add an indent of 4 to payload
|
|
18
|
+
payload = json.dumps(json.loads(payload), indent=4)
|
|
19
|
+
logger.log(self.level, "%s", payload)
|
nautobot/core/exceptions.py
CHANGED
nautobot/core/filters.py
CHANGED
|
@@ -618,7 +618,6 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
618
618
|
@staticmethod
|
|
619
619
|
def _get_filter_lookup_dict(existing_filter):
|
|
620
620
|
# Choose the lookup expression map based on the filter type
|
|
621
|
-
|
|
622
621
|
if isinstance(
|
|
623
622
|
existing_filter,
|
|
624
623
|
(
|
|
@@ -638,7 +637,6 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
638
637
|
(
|
|
639
638
|
django_filters.ModelChoiceFilter,
|
|
640
639
|
django_filters.ModelMultipleChoiceFilter,
|
|
641
|
-
MultiValueUUIDFilter,
|
|
642
640
|
TagFilter,
|
|
643
641
|
TreeNodeMultipleChoiceFilter,
|
|
644
642
|
),
|
|
@@ -668,7 +666,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
668
666
|
the form `<field_name>__<lookup_expr>`
|
|
669
667
|
"""
|
|
670
668
|
magic_filters = {}
|
|
671
|
-
if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in"
|
|
669
|
+
if filter_field.method is not None or filter_field.lookup_expr not in ["exact", "in"]:
|
|
672
670
|
return magic_filters
|
|
673
671
|
|
|
674
672
|
# Choose the lookup expression map based on the filter type
|
|
@@ -679,7 +677,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
679
677
|
|
|
680
678
|
# Get properties of the existing filter for later use
|
|
681
679
|
field_name = filter_field.field_name
|
|
682
|
-
field = get_model_field(cls._meta.model, field_name)
|
|
680
|
+
field = get_model_field(cls._meta.model, field_name)
|
|
683
681
|
|
|
684
682
|
# If there isn't a model field, return.
|
|
685
683
|
if field is None:
|
|
@@ -696,7 +694,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
696
694
|
new_filter_name = f"{filter_name}__{lookup_name}"
|
|
697
695
|
|
|
698
696
|
try:
|
|
699
|
-
if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}:
|
|
697
|
+
if filter_name in cls.declared_filters and lookup_expr not in {"isnull"}:
|
|
700
698
|
# The filter field has been explicitly defined on the filterset class so we must manually
|
|
701
699
|
# create the new filter with the same type because there is no guarantee the defined type
|
|
702
700
|
# is the same as the default type for the field. This does not apply if the filter
|
|
@@ -727,10 +725,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
727
725
|
# If the base filter_field has a custom label, django_filters won't adjust it for the new_filter lookup,
|
|
728
726
|
# so we have to do it.
|
|
729
727
|
if filter_field.label and filter_field.label != label_for_filter(
|
|
730
|
-
cls.
|
|
731
|
-
filter_field.field_name,
|
|
732
|
-
filter_field.lookup_expr,
|
|
733
|
-
filter_field.exclude,
|
|
728
|
+
cls.Meta.model, filter_field.field_name, filter_field.lookup_expr, filter_field.exclude
|
|
734
729
|
):
|
|
735
730
|
# Lightly adjusted from label_for_filter() implementation:
|
|
736
731
|
verbose_expression = ["exclude", filter_field.label] if new_filter.exclude else [filter_field.label]
|
|
@@ -753,22 +748,22 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
753
748
|
if not isinstance(new_filter_field, django_filters.Filter):
|
|
754
749
|
raise TypeError(f"Tried to add filter ({new_filter_name}) which is not an instance of Django Filter")
|
|
755
750
|
|
|
756
|
-
if new_filter_name in cls.base_filters:
|
|
751
|
+
if new_filter_name in cls.base_filters:
|
|
757
752
|
raise AttributeError(
|
|
758
753
|
f"There was a conflict with filter `{new_filter_name}`, the custom filter was ignored."
|
|
759
754
|
)
|
|
760
755
|
|
|
761
|
-
cls.base_filters[new_filter_name] = new_filter_field
|
|
756
|
+
cls.base_filters[new_filter_name] = new_filter_field
|
|
762
757
|
# django-filters has no concept of "abstract" filtersets, so we have to fake it
|
|
763
|
-
if cls._meta.model is not None:
|
|
764
|
-
cls.base_filters.update(
|
|
758
|
+
if cls._meta.model is not None:
|
|
759
|
+
cls.base_filters.update(
|
|
765
760
|
cls._generate_lookup_expression_filters(filter_name=new_filter_name, filter_field=new_filter_field)
|
|
766
761
|
)
|
|
767
762
|
|
|
768
763
|
@classmethod
|
|
769
764
|
def get_fields(cls):
|
|
770
765
|
fields = super().get_fields()
|
|
771
|
-
if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude):
|
|
766
|
+
if "id" not in fields and (cls._meta.exclude is None or "id" not in cls._meta.exclude):
|
|
772
767
|
# Add "id" as the first key in the `fields` dict
|
|
773
768
|
fields = {"id": [django_filters.conf.settings.DEFAULT_LOOKUP_EXPR], **fields}
|
|
774
769
|
return fields
|
|
@@ -785,7 +780,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
785
780
|
if filter_name.startswith("_"):
|
|
786
781
|
del filters[filter_name]
|
|
787
782
|
|
|
788
|
-
if getattr(cls._meta.model, "is_contact_associable_model", False):
|
|
783
|
+
if getattr(cls._meta.model, "is_contact_associable_model", False):
|
|
789
784
|
# Add "contacts" and "teams" filters
|
|
790
785
|
from nautobot.extras.models import Contact, Team
|
|
791
786
|
|
|
@@ -805,13 +800,13 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
805
800
|
label="Teams (name or ID)",
|
|
806
801
|
)
|
|
807
802
|
|
|
808
|
-
if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False):
|
|
809
|
-
if not hasattr(cls._meta.model, "static_group_association_set"):
|
|
803
|
+
if "dynamic_groups" not in filters and getattr(cls._meta.model, "is_dynamic_group_associable_model", False):
|
|
804
|
+
if not hasattr(cls._meta.model, "static_group_association_set"):
|
|
810
805
|
logger.warning(
|
|
811
806
|
"Model %s has 'is_dynamic_group_associable_model = True' but lacks "
|
|
812
807
|
"a 'static_group_association_set' attribute. Perhaps this is due to it inheriting from "
|
|
813
808
|
"the deprecated DynamicGroupMixin class instead of the preferred DynamicGroupsModelMixin?",
|
|
814
|
-
cls._meta.model,
|
|
809
|
+
cls._meta.model,
|
|
815
810
|
)
|
|
816
811
|
else:
|
|
817
812
|
# Add "dynamic_groups" field as the last key
|
|
@@ -821,14 +816,14 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
821
816
|
queryset=DynamicGroup.objects.all(),
|
|
822
817
|
field_name="static_group_association_set__dynamic_group",
|
|
823
818
|
to_field_name="name",
|
|
824
|
-
query_params={"content_type": cls._meta.model._meta.label_lower},
|
|
819
|
+
query_params={"content_type": cls._meta.model._meta.label_lower},
|
|
825
820
|
label="Dynamic groups (name or ID)",
|
|
826
821
|
)
|
|
827
822
|
|
|
828
823
|
# django-filters has no concept of "abstract" filtersets, so we have to fake it
|
|
829
|
-
if cls._meta.model is not None:
|
|
824
|
+
if cls._meta.model is not None:
|
|
830
825
|
if "tags" in filters and isinstance(filters["tags"], TagFilter):
|
|
831
|
-
filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]}
|
|
826
|
+
filters["tags"].extra["query_params"] = {"content_types": [cls._meta.model._meta.label_lower]}
|
|
832
827
|
|
|
833
828
|
new_filters = {}
|
|
834
829
|
for existing_filter_name, existing_filter in filters.items():
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"model": "users.user",
|
|
4
|
+
"pk": "90798182-9d19-479a-899f-a415fc2c62fe",
|
|
5
|
+
"fields": {
|
|
6
|
+
"password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
|
|
7
|
+
"last_login": null,
|
|
8
|
+
"is_superuser": false,
|
|
9
|
+
"username": "alice",
|
|
10
|
+
"first_name": "",
|
|
11
|
+
"last_name": "",
|
|
12
|
+
"email": "alice@localhost",
|
|
13
|
+
"is_staff": false,
|
|
14
|
+
"is_active": true,
|
|
15
|
+
"date_joined": "2021-05-25T23:49:55.008Z",
|
|
16
|
+
"config_data": {},
|
|
17
|
+
"groups": [],
|
|
18
|
+
"user_permissions": []
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"model": "users.user",
|
|
23
|
+
"pk": "90798182-9d19-479a-899f-a415fc2c62fd",
|
|
24
|
+
"fields": {
|
|
25
|
+
"password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
|
|
26
|
+
"last_login": null,
|
|
27
|
+
"is_superuser": true,
|
|
28
|
+
"username": "bob",
|
|
29
|
+
"first_name": "",
|
|
30
|
+
"last_name": "",
|
|
31
|
+
"email": "bob@localhost",
|
|
32
|
+
"is_staff": true,
|
|
33
|
+
"is_active": true,
|
|
34
|
+
"date_joined": "2021-05-25T23:49:55.008Z",
|
|
35
|
+
"config_data": {},
|
|
36
|
+
"groups": [],
|
|
37
|
+
"user_permissions": []
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"model": "users.user",
|
|
42
|
+
"pk": "90798182-9d19-479a-899f-a415fc2c62ff",
|
|
43
|
+
"fields": {
|
|
44
|
+
"password": "pbkdf2_sha256$216000$TtNpNWboQq6C$6vh6qqRXUGSuLZlwvvGRbz35BS6avrdEFudnjJv2HXY=",
|
|
45
|
+
"last_login": null,
|
|
46
|
+
"is_superuser": false,
|
|
47
|
+
"username": "charlie",
|
|
48
|
+
"first_name": "",
|
|
49
|
+
"last_name": "",
|
|
50
|
+
"email": "charlie@localhost",
|
|
51
|
+
"is_staff": false,
|
|
52
|
+
"is_active": true,
|
|
53
|
+
"date_joined": "2021-05-25T23:49:55.008Z",
|
|
54
|
+
"config_data": {},
|
|
55
|
+
"groups": [],
|
|
56
|
+
"user_permissions": []
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
]
|
nautobot/core/forms/fields.py
CHANGED
|
@@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
8
8
|
from django.contrib.postgres.forms import SimpleArrayField
|
|
9
9
|
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist, ValidationError
|
|
10
10
|
from django.db.models import Q
|
|
11
|
-
from django.forms.fields import BoundField, InvalidJSONInput, JSONField as _JSONField
|
|
11
|
+
from django.forms.fields import BoundField, CallableChoiceIterator, InvalidJSONInput, JSONField as _JSONField
|
|
12
12
|
from django.templatetags.static import static
|
|
13
13
|
from django.urls import reverse
|
|
14
14
|
from django.utils.html import format_html
|
|
@@ -102,13 +102,13 @@ class CSVFileField(django_forms.FileField):
|
|
|
102
102
|
"in double quotes."
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
-
def to_python(self,
|
|
105
|
+
def to_python(self, file):
|
|
106
106
|
"""For parity with CSVDataField, this returns the CSV text rather than an UploadedFile object."""
|
|
107
|
-
if
|
|
107
|
+
if file is None:
|
|
108
108
|
return None
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
return
|
|
110
|
+
file = super().to_python(file)
|
|
111
|
+
return file.read().decode("utf-8-sig").strip()
|
|
112
112
|
|
|
113
113
|
|
|
114
114
|
class CSVChoiceField(django_forms.ChoiceField):
|
|
@@ -660,11 +660,29 @@ class JSONArrayFormField(django_forms.JSONField):
|
|
|
660
660
|
and each Array element is validated by `base_field` validators.
|
|
661
661
|
"""
|
|
662
662
|
|
|
663
|
-
def __init__(self, base_field, *, delimiter=",", **kwargs):
|
|
663
|
+
def __init__(self, base_field, *, choices=None, delimiter=",", **kwargs):
|
|
664
|
+
self.has_choices = False
|
|
665
|
+
if choices:
|
|
666
|
+
self.choices = choices
|
|
667
|
+
self.widget = widgets.StaticSelect2Multiple(choices=choices)
|
|
668
|
+
self.has_choices = True
|
|
664
669
|
self.base_field = base_field
|
|
665
670
|
self.delimiter = delimiter
|
|
666
671
|
super().__init__(**kwargs)
|
|
667
672
|
|
|
673
|
+
# TODO: change this when we upgrade to Django 5, it uses a getter/setter for choices
|
|
674
|
+
def _get_choices(self):
|
|
675
|
+
return getattr(self, "_choices", None)
|
|
676
|
+
|
|
677
|
+
def _set_choices(self, value):
|
|
678
|
+
if callable(value):
|
|
679
|
+
value = CallableChoiceIterator(value)
|
|
680
|
+
else:
|
|
681
|
+
value = list(value)
|
|
682
|
+
self._choices = self.widget.choices = value
|
|
683
|
+
|
|
684
|
+
choices = property(_get_choices, _set_choices)
|
|
685
|
+
|
|
668
686
|
def clean(self, value):
|
|
669
687
|
"""
|
|
670
688
|
Validate `value` and return its "cleaned" value as an appropriate
|
|
@@ -677,10 +695,21 @@ class JSONArrayFormField(django_forms.JSONField):
|
|
|
677
695
|
"""
|
|
678
696
|
Return a string of this value.
|
|
679
697
|
"""
|
|
680
|
-
if
|
|
698
|
+
if self.has_choices:
|
|
699
|
+
if isinstance(value, list):
|
|
700
|
+
return value
|
|
701
|
+
return [value]
|
|
702
|
+
elif isinstance(value, list):
|
|
681
703
|
return self.delimiter.join(str(self.base_field.prepare_value(v)) for v in value)
|
|
682
704
|
return value
|
|
683
705
|
|
|
706
|
+
def bound_data(self, data, initial):
|
|
707
|
+
if data is None:
|
|
708
|
+
return None
|
|
709
|
+
if isinstance(data, list):
|
|
710
|
+
data = json.dumps(data)
|
|
711
|
+
return super().bound_data(data, initial)
|
|
712
|
+
|
|
684
713
|
def to_python(self, value):
|
|
685
714
|
"""
|
|
686
715
|
Convert `value` into JSON, raising django.core.exceptions.ValidationError
|
|
@@ -718,9 +747,25 @@ class JSONArrayFormField(django_forms.JSONField):
|
|
|
718
747
|
self.base_field.validate(item)
|
|
719
748
|
except ValidationError as error:
|
|
720
749
|
errors.append(error)
|
|
750
|
+
if self.has_choices and not self.valid_value(item):
|
|
751
|
+
errors.append(ValidationError(f"{item} is not a valid choice"))
|
|
721
752
|
if errors:
|
|
722
753
|
raise ValidationError(errors)
|
|
723
754
|
|
|
755
|
+
def valid_value(self, value):
|
|
756
|
+
"""Check to see if the provided value is a valid choice."""
|
|
757
|
+
text_value = str(value)
|
|
758
|
+
for k, v in self.choices:
|
|
759
|
+
if isinstance(v, (list, tuple)):
|
|
760
|
+
# This is an optgroup, so look inside the group for options
|
|
761
|
+
for k2, _ in v:
|
|
762
|
+
if value == k2 or text_value == str(k2):
|
|
763
|
+
return True
|
|
764
|
+
else:
|
|
765
|
+
if value == k or text_value == str(k):
|
|
766
|
+
return True
|
|
767
|
+
return False
|
|
768
|
+
|
|
724
769
|
def run_validators(self, value):
|
|
725
770
|
"""
|
|
726
771
|
Runs all validators against `value` and raise ValidationError if necessary.
|
|
@@ -774,7 +819,7 @@ class MultiMatchModelMultipleChoiceField(DynamicModelChoiceMixin, django_filters
|
|
|
774
819
|
self.natural_key = kwargs.setdefault("to_field_name", "slug")
|
|
775
820
|
super().__init__(*args, **kwargs)
|
|
776
821
|
|
|
777
|
-
def _check_values(self, values):
|
|
822
|
+
def _check_values(self, values):
|
|
778
823
|
"""
|
|
779
824
|
This method overloads the grandparent method in `django.forms.models.ModelMultipleChoiceField`,
|
|
780
825
|
re-using some of that method's existing logic and adding support for coupling this field with
|