nautobot 2.4.0b1__py3-none-any.whl → 2.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/__init__.py +1 -1
- nautobot/apps/api.py +8 -8
- nautobot/apps/change_logging.py +2 -2
- nautobot/apps/choices.py +4 -4
- nautobot/apps/events.py +3 -3
- 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 +2 -2
- nautobot/apps/utils.py +7 -7
- nautobot/apps/views.py +7 -7
- nautobot/circuits/api/serializers.py +1 -0
- nautobot/circuits/api/views.py +4 -8
- nautobot/circuits/tables.py +2 -1
- nautobot/circuits/templates/circuits/circuit_create.html +1 -7
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/circuits/views.py +3 -3
- nautobot/cloud/api/views.py +6 -10
- nautobot/cloud/models.py +1 -1
- 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/schema.py +13 -2
- nautobot/core/api/serializers.py +40 -34
- nautobot/core/api/views.py +56 -4
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/celery/log.py +4 -4
- nautobot/core/celery/schedulers.py +2 -2
- nautobot/core/choices.py +2 -2
- nautobot/core/events/__init__.py +3 -3
- nautobot/core/filters.py +67 -35
- nautobot/core/forms/__init__.py +19 -19
- nautobot/core/forms/fields.py +14 -11
- nautobot/core/forms/forms.py +33 -2
- nautobot/core/graphql/types.py +1 -1
- nautobot/core/jobs/__init__.py +28 -7
- nautobot/core/jobs/bulk_actions.py +285 -0
- nautobot/core/jobs/cleanup.py +48 -12
- nautobot/core/jobs/groups.py +1 -1
- nautobot/core/management/commands/validate_models.py +1 -1
- nautobot/core/models/__init__.py +3 -1
- nautobot/core/models/query_functions.py +2 -2
- nautobot/core/models/tree_queries.py +6 -3
- nautobot/core/settings.py +29 -2
- nautobot/core/settings.yaml +21 -0
- nautobot/core/tables.py +79 -61
- nautobot/core/templates/about.html +67 -0
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/inc/nav_menu.html +1 -0
- 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 +13 -0
- nautobot/core/templates/search.html +7 -0
- nautobot/core/templates/utilities/render_jinja2.html +1 -1
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/theme_preview.html +7 -0
- nautobot/core/templatetags/helpers.py +11 -2
- nautobot/core/testing/__init__.py +8 -8
- nautobot/core/testing/api.py +170 -15
- nautobot/core/testing/filters.py +45 -10
- nautobot/core/testing/forms.py +2 -0
- nautobot/core/testing/integration.py +514 -5
- nautobot/core/testing/mixins.py +7 -2
- nautobot/core/testing/views.py +44 -29
- 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 +1 -0
- nautobot/core/tests/runner.py +1 -1
- nautobot/core/tests/test_api.py +98 -1
- nautobot/core/tests/test_csv.py +25 -3
- nautobot/core/tests/test_filters.py +209 -246
- nautobot/core/tests/test_forms.py +1 -0
- nautobot/core/tests/test_jobs.py +492 -1
- nautobot/core/tests/test_models.py +9 -0
- nautobot/core/tests/test_settings_schema.py +7 -0
- nautobot/core/tests/test_tables.py +100 -0
- nautobot/core/tests/test_utils.py +63 -1
- nautobot/core/tests/test_views.py +30 -3
- nautobot/core/ui/nav.py +1 -0
- nautobot/core/ui/object_detail.py +15 -1
- nautobot/core/urls.py +11 -0
- nautobot/core/utils/git.py +7 -2
- nautobot/core/utils/lookup.py +11 -8
- nautobot/core/utils/querysets.py +64 -0
- nautobot/core/utils/requests.py +24 -9
- nautobot/core/views/__init__.py +42 -0
- nautobot/core/views/generic.py +131 -197
- nautobot/core/views/mixins.py +136 -41
- nautobot/core/views/renderers.py +6 -6
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/api/serializers.py +56 -64
- nautobot/dcim/api/views.py +47 -113
- nautobot/dcim/constants.py +6 -13
- nautobot/dcim/factory.py +6 -1
- nautobot/dcim/filters/__init__.py +31 -2
- nautobot/dcim/forms.py +48 -17
- nautobot/dcim/graphql/types.py +2 -2
- nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
- nautobot/dcim/models/__init__.py +1 -1
- nautobot/dcim/models/device_component_templates.py +2 -2
- nautobot/dcim/models/device_components.py +22 -20
- nautobot/dcim/models/devices.py +10 -1
- nautobot/dcim/models/locations.py +3 -3
- nautobot/dcim/models/power.py +6 -5
- nautobot/dcim/models/racks.py +4 -4
- nautobot/dcim/tables/__init__.py +3 -3
- nautobot/dcim/tables/devices.py +9 -6
- nautobot/dcim/tables/devicetypes.py +2 -2
- nautobot/dcim/tables/racks.py +1 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/controller_create.html +1 -7
- nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device.html +3 -11
- nautobot/dcim/templates/dcim/device_component.html +1 -1
- nautobot/dcim/templates/dcim/device_edit.html +36 -37
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/location.html +2 -10
- nautobot/dcim/templates/dcim/location_edit.html +1 -7
- nautobot/dcim/templates/dcim/locationtype.html +1 -1
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -1
- nautobot/dcim/templates/dcim/platform.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/rack.html +2 -10
- nautobot/dcim/templates/dcim/rack_edit.html +1 -7
- nautobot/dcim/templates/dcim/rackgroup.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation.html +3 -11
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
- nautobot/dcim/tests/integration/test_controller.py +62 -0
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- nautobot/dcim/tests/test_api.py +16 -5
- nautobot/dcim/tests/test_filters.py +33 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/dcim/tests/test_graphql.py +52 -0
- nautobot/dcim/tests/test_jobs.py +118 -0
- nautobot/dcim/tests/test_models.py +52 -9
- nautobot/dcim/tests/test_views.py +30 -84
- nautobot/dcim/views.py +13 -28
- nautobot/extras/api/customfields.py +2 -2
- nautobot/extras/api/serializers.py +123 -85
- nautobot/extras/api/views.py +33 -30
- nautobot/extras/constants.py +3 -0
- nautobot/extras/datasources/git.py +125 -0
- nautobot/extras/filters/__init__.py +8 -6
- nautobot/extras/forms/base.py +2 -2
- nautobot/extras/forms/forms.py +139 -31
- nautobot/extras/forms/mixins.py +14 -6
- nautobot/extras/group_sync.py +3 -3
- nautobot/extras/health_checks.py +1 -2
- nautobot/extras/jobs.py +85 -18
- nautobot/extras/managers.py +3 -1
- nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
- 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/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
- nautobot/extras/models/__init__.py +1 -1
- nautobot/extras/models/contacts.py +1 -1
- nautobot/extras/models/customfields.py +41 -23
- nautobot/extras/models/datasources.py +85 -0
- nautobot/extras/models/groups.py +11 -9
- nautobot/extras/models/jobs.py +23 -4
- nautobot/extras/models/models.py +17 -2
- nautobot/extras/models/relationships.py +17 -5
- nautobot/extras/plugins/__init__.py +13 -2
- nautobot/extras/plugins/marketplace_manifest.yml +84 -79
- nautobot/extras/plugins/tables.py +16 -14
- nautobot/extras/plugins/views.py +65 -69
- nautobot/extras/registry.py +1 -1
- nautobot/extras/secrets/__init__.py +2 -2
- nautobot/extras/signals.py +15 -1
- nautobot/extras/tables.py +7 -5
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/customlink.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup.html +2 -10
- nautobot/extras/templates/extras/exporttemplate.html +1 -1
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/graphqlquery.html +1 -1
- nautobot/extras/templates/extras/job_detail.html +17 -1
- nautobot/extras/templates/extras/job_edit.html +1 -0
- nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
- nautobot/extras/templates/extras/jobhook.html +1 -1
- nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/marketplace.html +29 -11
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/plugin_detail.html +33 -16
- nautobot/extras/templates/extras/plugins_tiles.html +21 -10
- nautobot/extras/templates/extras/relationship.html +1 -63
- nautobot/extras/templates/extras/role_retrieve.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +1 -1
- nautobot/extras/templates/extras/secret.html +1 -1
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/webhook.html +1 -1
- nautobot/extras/templatetags/job_buttons.py +4 -4
- 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/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
- nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
- nautobot/extras/tests/git_helper.py +9 -1
- nautobot/extras/tests/integration/__init__.py +29 -16
- nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
- nautobot/extras/tests/test_api.py +19 -13
- nautobot/extras/tests/test_customfields.py +50 -52
- nautobot/extras/tests/test_datasources.py +29 -1
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_filters.py +6 -6
- nautobot/extras/tests/test_forms.py +33 -1
- nautobot/extras/tests/test_jobs.py +178 -32
- nautobot/extras/tests/test_models.py +299 -10
- nautobot/extras/tests/test_plugins.py +62 -9
- nautobot/extras/tests/test_relationships.py +120 -9
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/tests/test_views.py +56 -194
- nautobot/extras/utils.py +20 -10
- nautobot/extras/views.py +85 -110
- nautobot/ipam/api/fields.py +3 -3
- nautobot/ipam/api/serializers.py +41 -33
- nautobot/ipam/api/views.py +68 -117
- nautobot/ipam/factory.py +1 -1
- nautobot/ipam/filters.py +3 -2
- nautobot/ipam/lookups.py +101 -62
- nautobot/ipam/models.py +74 -18
- nautobot/ipam/querysets.py +2 -2
- nautobot/ipam/tables.py +25 -9
- nautobot/ipam/templates/ipam/ipaddress.html +2 -10
- 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 +2 -10
- nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
- nautobot/ipam/templates/ipam/rir.html +1 -1
- nautobot/ipam/templates/ipam/routetarget.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +2 -10
- nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
- nautobot/ipam/templates/ipam/vlangroup.html +1 -1
- nautobot/ipam/templates/ipam/vrf.html +1 -1
- nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
- nautobot/ipam/tests/test_api.py +436 -3
- nautobot/ipam/tests/test_forms.py +49 -47
- nautobot/ipam/tests/test_migrations.py +30 -30
- nautobot/ipam/tests/test_models.py +119 -34
- nautobot/ipam/tests/test_querysets.py +63 -1
- nautobot/ipam/tests/test_utils.py +41 -2
- nautobot/ipam/tests/test_views.py +3 -0
- nautobot/ipam/utils/__init__.py +54 -17
- nautobot/ipam/views.py +61 -87
- 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/docs/404.html +131 -14
- nautobot/project-static/docs/apps/index.html +131 -14
- nautobot/project-static/docs/apps/nautobot-apps.html +132 -16
- nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.60a45f97.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.60a45f97.min.js.map} +2 -2
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +147 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +144 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +459 -132
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +175 -28
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +180 -31
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +138 -16
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +137 -15
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +164 -27
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +187 -38
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +193 -31
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +216 -48
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +324 -75
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +666 -175
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +194 -46
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +538 -177
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +578 -221
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +145 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +156 -25
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +492 -65
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +705 -215
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +943 -422
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +144 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +619 -200
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +474 -159
- nautobot/project-static/docs/development/apps/api/configuration-view.html +131 -14
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +131 -14
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +131 -14
- nautobot/project-static/docs/development/apps/api/models/global-search.html +131 -14
- nautobot/project-static/docs/development/apps/api/models/graphql.html +131 -14
- nautobot/project-static/docs/development/apps/api/models/index.html +131 -14
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +131 -14
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +153 -17
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +131 -14
- nautobot/project-static/docs/development/apps/api/prometheus.html +131 -14
- nautobot/project-static/docs/development/apps/api/setup.html +131 -14
- nautobot/project-static/docs/development/apps/api/testing.html +131 -14
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +131 -14
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +131 -14
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +131 -14
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +131 -14
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/base-template.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/index.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/notes.html +131 -14
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +137 -16
- nautobot/project-static/docs/development/apps/api/views/urls.html +131 -14
- nautobot/project-static/docs/development/apps/index.html +131 -14
- nautobot/project-static/docs/development/apps/migration/code-updates.html +131 -14
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +131 -14
- nautobot/project-static/docs/development/apps/migration/from-v1.html +131 -14
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +131 -14
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +131 -14
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +131 -14
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +131 -14
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +135 -18
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +131 -14
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +297 -25
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +131 -14
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +131 -14
- nautobot/project-static/docs/development/core/application-registry.html +131 -14
- nautobot/project-static/docs/development/core/best-practices.html +131 -14
- nautobot/project-static/docs/development/core/bootstrap-ui.html +131 -14
- nautobot/project-static/docs/development/core/caching.html +131 -14
- nautobot/project-static/docs/development/core/controllers.html +131 -14
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +158 -84
- nautobot/project-static/docs/development/core/generic-views.html +131 -14
- nautobot/project-static/docs/development/core/getting-started.html +334 -234
- nautobot/project-static/docs/development/core/homepage.html +134 -17
- nautobot/project-static/docs/development/core/index.html +131 -14
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9829 -0
- nautobot/project-static/docs/development/core/model-checklist.html +141 -22
- nautobot/project-static/docs/development/core/model-features.html +131 -14
- nautobot/project-static/docs/development/core/natural-keys.html +131 -14
- nautobot/project-static/docs/development/core/navigation-menu.html +131 -14
- nautobot/project-static/docs/development/core/release-checklist.html +134 -17
- nautobot/project-static/docs/development/core/role-internals.html +131 -14
- nautobot/project-static/docs/development/core/settings.html +131 -14
- nautobot/project-static/docs/development/core/style-guide.html +134 -17
- nautobot/project-static/docs/development/core/templates.html +132 -15
- nautobot/project-static/docs/development/core/testing.html +131 -14
- nautobot/project-static/docs/development/core/ui-component-framework.html +454 -283
- nautobot/project-static/docs/development/core/user-preferences.html +131 -14
- nautobot/project-static/docs/development/index.html +131 -14
- nautobot/project-static/docs/development/jobs/index.html +301 -132
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +131 -14
- nautobot/project-static/docs/index.html +139 -33
- 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/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/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/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +132 -17
- nautobot/project-static/docs/overview/design_philosophy.html +131 -14
- nautobot/project-static/docs/release-notes/index.html +137 -22
- nautobot/project-static/docs/release-notes/version-1.0.html +319 -203
- nautobot/project-static/docs/release-notes/version-1.1.html +316 -200
- nautobot/project-static/docs/release-notes/version-1.2.html +391 -275
- nautobot/project-static/docs/release-notes/version-1.3.html +417 -301
- nautobot/project-static/docs/release-notes/version-1.4.html +502 -387
- nautobot/project-static/docs/release-notes/version-1.5.html +690 -576
- nautobot/project-static/docs/release-notes/version-1.6.html +989 -457
- nautobot/project-static/docs/release-notes/version-2.0.html +613 -499
- nautobot/project-static/docs/release-notes/version-2.1.html +448 -334
- nautobot/project-static/docs/release-notes/version-2.2.html +441 -327
- nautobot/project-static/docs/release-notes/version-2.3.html +1171 -451
- nautobot/project-static/docs/release-notes/version-2.4.html +800 -111
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +303 -287
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +131 -14
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +131 -14
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +133 -16
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +131 -14
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +131 -14
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +195 -18
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +134 -17
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +131 -14
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +133 -16
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +131 -14
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +131 -14
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +131 -14
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +151 -18
- nautobot/project-static/docs/user-guide/administration/installation/index.html +131 -14
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +132 -15
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +131 -14
- nautobot/project-static/docs/user-guide/administration/installation/services.html +131 -14
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +131 -14
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +131 -14
- nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +131 -14
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +134 -18
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +131 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +135 -22
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +134 -17
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +131 -14
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +134 -17
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +236 -34
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +131 -14
- 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 +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +131 -14
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +134 -17
- nautobot/project-static/docs/{development/core/local-k8s.html → user-guide/feature-guides/wireless-networks-and-controllers.html} +632 -566
- nautobot/project-static/docs/user-guide/index.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +135 -18
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +451 -16
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +135 -17
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +134 -17
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9797 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +132 -15
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +179 -35
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +159 -15
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +131 -14
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +131 -14
- nautobot/project-static/js/forms.js +1 -1
- nautobot/tenancy/api/views.py +9 -13
- nautobot/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/views.py +4 -2
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +5 -4
- nautobot/users/api/views.py +3 -3
- nautobot/virtualization/api/serializers.py +4 -4
- nautobot/virtualization/api/views.py +5 -24
- nautobot/virtualization/filters.py +20 -3
- nautobot/virtualization/models.py +1 -1
- nautobot/virtualization/tables.py +2 -2
- nautobot/virtualization/templates/virtualization/cluster.html +1 -1
- nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -10
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
- nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
- nautobot/virtualization/tests/test_filters.py +17 -0
- nautobot/wireless/filters.py +2 -2
- nautobot/wireless/forms.py +1 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
- nautobot/wireless/tests/integration/__init__.py +0 -0
- nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
- nautobot/wireless/tests/test_filters.py +29 -1
- nautobot/wireless/tests/test_views.py +22 -1
- nautobot/wireless/views.py +0 -10
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/METADATA +9 -9
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/RECORD +667 -610
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +1 -1
- nautobot/core/fixtures/user-data.json +0 -59
- /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
- {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
nautobot/extras/jobs.py
CHANGED
|
@@ -18,6 +18,7 @@ from db_file_storage.form_widgets import DBClearableFileInput
|
|
|
18
18
|
from django import forms
|
|
19
19
|
from django.conf import settings
|
|
20
20
|
from django.contrib.auth import get_user_model
|
|
21
|
+
from django.core.cache import cache
|
|
21
22
|
from django.core.exceptions import ObjectDoesNotExist
|
|
22
23
|
from django.core.files.base import ContentFile
|
|
23
24
|
from django.core.files.uploadedfile import UploadedFile
|
|
@@ -67,15 +68,15 @@ User = get_user_model()
|
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
__all__ = [
|
|
70
|
-
"Job",
|
|
71
71
|
"BooleanVar",
|
|
72
72
|
"ChoiceVar",
|
|
73
73
|
"FileVar",
|
|
74
|
-
"IntegerVar",
|
|
75
74
|
"IPAddressVar",
|
|
76
75
|
"IPAddressWithMaskVar",
|
|
77
76
|
"IPNetworkVar",
|
|
77
|
+
"IntegerVar",
|
|
78
78
|
"JSONVar",
|
|
79
|
+
"Job",
|
|
79
80
|
"MultiChoiceVar",
|
|
80
81
|
"MultiObjectVar",
|
|
81
82
|
"ObjectVar",
|
|
@@ -105,13 +106,16 @@ class BaseJob:
|
|
|
105
106
|
|
|
106
107
|
- name (str)
|
|
107
108
|
- description (str)
|
|
108
|
-
- hidden (bool)
|
|
109
|
-
- field_order (list)
|
|
110
109
|
- approval_required (bool)
|
|
111
|
-
-
|
|
112
|
-
-
|
|
110
|
+
- dryrun_default (bool)
|
|
111
|
+
- field_order (list)
|
|
113
112
|
- has_sensitive_variables (bool)
|
|
113
|
+
- hidden (bool)
|
|
114
|
+
- soft_time_limit (int)
|
|
114
115
|
- task_queues (list)
|
|
116
|
+
- template_name (str)
|
|
117
|
+
- time_limit (int)
|
|
118
|
+
- is_singleton (bool)
|
|
115
119
|
"""
|
|
116
120
|
|
|
117
121
|
def __init__(self):
|
|
@@ -189,6 +193,31 @@ class BaseJob:
|
|
|
189
193
|
)
|
|
190
194
|
raise RunJobTaskFailed(f"Job {self.job_model} is not enabled to be run!")
|
|
191
195
|
|
|
196
|
+
ignore_singleton_lock = self.celery_kwargs.get("nautobot_job_ignore_singleton_lock", False)
|
|
197
|
+
if self.job_model.is_singleton:
|
|
198
|
+
is_running = cache.get(self.singleton_cache_key)
|
|
199
|
+
if is_running:
|
|
200
|
+
if ignore_singleton_lock:
|
|
201
|
+
self.logger.info(
|
|
202
|
+
"Job %s is a singleton and already running, but singleton will be ignored because"
|
|
203
|
+
" `ignore_singleton_lock` is set.",
|
|
204
|
+
self.job_model,
|
|
205
|
+
extra={"object": self.job_model, "grouping": "initialization"},
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
self.logger.error(
|
|
209
|
+
"Job %s is a singleton and already running.",
|
|
210
|
+
self.job_model,
|
|
211
|
+
extra={"object": self.job_model, "grouping": "initialization"},
|
|
212
|
+
)
|
|
213
|
+
raise RunJobTaskFailed(f"Job '{self.job_model}' is a singleton and already running.")
|
|
214
|
+
cache_parameters = {
|
|
215
|
+
"key": self.singleton_cache_key,
|
|
216
|
+
"value": 1,
|
|
217
|
+
"timeout": self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT,
|
|
218
|
+
}
|
|
219
|
+
cache.set(**cache_parameters)
|
|
220
|
+
|
|
192
221
|
soft_time_limit = self.job_model.soft_time_limit or settings.CELERY_TASK_SOFT_TIME_LIMIT
|
|
193
222
|
time_limit = self.job_model.time_limit or settings.CELERY_TASK_TIME_LIMIT
|
|
194
223
|
if time_limit <= soft_time_limit:
|
|
@@ -282,6 +311,14 @@ class BaseJob:
|
|
|
282
311
|
if status == JobResultStatusChoices.STATUS_SUCCESS:
|
|
283
312
|
self.logger.success("Job completed", extra={"grouping": "post_run"})
|
|
284
313
|
|
|
314
|
+
cache.delete(self.singleton_cache_key)
|
|
315
|
+
|
|
316
|
+
@final
|
|
317
|
+
@classproperty
|
|
318
|
+
def singleton_cache_key(cls) -> str: # pylint: disable=no-self-argument
|
|
319
|
+
"""Cache key for singleton jobs."""
|
|
320
|
+
return f"nautobot.extras.jobs.running.{cls.class_path}"
|
|
321
|
+
|
|
285
322
|
@final
|
|
286
323
|
@classproperty
|
|
287
324
|
def file_path(cls) -> str: # pylint: disable=no-self-argument
|
|
@@ -300,7 +337,7 @@ class BaseJob:
|
|
|
300
337
|
- my_plugin.jobs.MyPluginJob - App-provided Job
|
|
301
338
|
- git_repository.jobs.myjob.MyJob - GitRepository Job
|
|
302
339
|
"""
|
|
303
|
-
return f"{cls.__module__}.{cls.__name__}"
|
|
340
|
+
return f"{cls.__module__}.{cls.__name__}" # pylint: disable=no-member
|
|
304
341
|
|
|
305
342
|
@final
|
|
306
343
|
@classproperty
|
|
@@ -337,7 +374,7 @@ class BaseJob:
|
|
|
337
374
|
@final
|
|
338
375
|
@classproperty
|
|
339
376
|
def name(cls) -> str: # pylint: disable=no-self-argument
|
|
340
|
-
return cls._get_meta_attr_and_assert_type("name", cls.__name__, expected_type=str)
|
|
377
|
+
return cls._get_meta_attr_and_assert_type("name", cls.__name__, expected_type=str) # pylint: disable=no-member
|
|
341
378
|
|
|
342
379
|
@final
|
|
343
380
|
@classproperty
|
|
@@ -401,6 +438,11 @@ class BaseJob:
|
|
|
401
438
|
def task_queues(cls) -> list: # pylint: disable=no-self-argument
|
|
402
439
|
return cls._get_meta_attr_and_assert_type("task_queues", [], expected_type=(list, tuple))
|
|
403
440
|
|
|
441
|
+
@final
|
|
442
|
+
@classproperty
|
|
443
|
+
def is_singleton(cls) -> bool: # pylint: disable=no-self-argument
|
|
444
|
+
return cls._get_meta_attr_and_assert_type("is_singleton", False, expected_type=bool)
|
|
445
|
+
|
|
404
446
|
@final
|
|
405
447
|
@classproperty
|
|
406
448
|
def properties_dict(cls) -> dict: # pylint: disable=no-self-argument
|
|
@@ -419,13 +461,14 @@ class BaseJob:
|
|
|
419
461
|
"time_limit": cls.time_limit,
|
|
420
462
|
"has_sensitive_variables": cls.has_sensitive_variables,
|
|
421
463
|
"task_queues": cls.task_queues,
|
|
464
|
+
"is_singleton": cls.is_singleton,
|
|
422
465
|
}
|
|
423
466
|
|
|
424
467
|
@final
|
|
425
468
|
@classproperty
|
|
426
469
|
def registered_name(cls) -> str: # pylint: disable=no-self-argument
|
|
427
470
|
"""Deprecated - use class_path classproperty instead."""
|
|
428
|
-
return f"{cls.__module__}.{cls.__name__}"
|
|
471
|
+
return f"{cls.__module__}.{cls.__name__}" # pylint: disable=no-member
|
|
429
472
|
|
|
430
473
|
@classmethod
|
|
431
474
|
def _get_vars(cls):
|
|
@@ -486,11 +529,29 @@ class BaseJob:
|
|
|
486
529
|
label="Profile job execution",
|
|
487
530
|
help_text="Profiles the job execution using cProfile and outputs a report to /tmp/",
|
|
488
531
|
)
|
|
532
|
+
# If the class already exists there may be overrides, so we have to check this.
|
|
533
|
+
try:
|
|
534
|
+
job_model = JobModel.objects.get(module_name=cls.__module__, job_class_name=cls.__name__)
|
|
535
|
+
is_singleton = job_model.is_singleton
|
|
536
|
+
except JobModel.DoesNotExist:
|
|
537
|
+
logger.warning("No Job instance found in the database corresponding to %s", cls.class_path)
|
|
538
|
+
job_model = None
|
|
539
|
+
is_singleton = cls.is_singleton
|
|
540
|
+
|
|
541
|
+
if is_singleton:
|
|
542
|
+
form.fields["_ignore_singleton_lock"] = forms.BooleanField(
|
|
543
|
+
required=False,
|
|
544
|
+
initial=False,
|
|
545
|
+
label="Ignore singleton lock",
|
|
546
|
+
help_text="Allow this singleton job to run even when another instance is already running",
|
|
547
|
+
)
|
|
489
548
|
|
|
490
|
-
job_model
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
549
|
+
if job_model is not None:
|
|
550
|
+
job_queue_queryset = JobQueue.objects.filter(jobs=job_model)
|
|
551
|
+
job_queue_params = {"jobs": [job_model.pk]}
|
|
552
|
+
else:
|
|
553
|
+
job_queue_queryset = JobQueue.objects.all()
|
|
554
|
+
job_queue_params = {}
|
|
494
555
|
|
|
495
556
|
# Initialize job_queue choices
|
|
496
557
|
form.fields["_job_queue"] = DynamicModelChoiceField(
|
|
@@ -500,8 +561,12 @@ class BaseJob:
|
|
|
500
561
|
help_text="The job queue to route this job to",
|
|
501
562
|
label="Job queue",
|
|
502
563
|
)
|
|
503
|
-
|
|
504
|
-
|
|
564
|
+
|
|
565
|
+
dryrun_default = cls.dryrun_default
|
|
566
|
+
if job_model is not None:
|
|
567
|
+
form.fields["_job_queue"].initial = job_model.default_job_queue.pk
|
|
568
|
+
if job_model.dryrun_default_override:
|
|
569
|
+
dryrun_default = job_model.dryrun_default
|
|
505
570
|
|
|
506
571
|
if cls.supports_dryrun and (not initial or "dryrun" not in initial):
|
|
507
572
|
# Set initial "dryrun" checkbox state based on the Meta parameter
|
|
@@ -519,7 +584,9 @@ class BaseJob:
|
|
|
519
584
|
field.disabled = True
|
|
520
585
|
|
|
521
586
|
# Ensure non-Job-specific fields are still last after applying field_order
|
|
522
|
-
for field in ["_job_queue", "_profile"]:
|
|
587
|
+
for field in ["_job_queue", "_profile", "_ignore_singleton_lock"]:
|
|
588
|
+
if field not in form.fields:
|
|
589
|
+
continue
|
|
523
590
|
value = form.fields.pop(field)
|
|
524
591
|
form.fields[field] = value
|
|
525
592
|
|
|
@@ -1031,7 +1098,7 @@ class JobHookReceiver(Job):
|
|
|
1031
1098
|
|
|
1032
1099
|
object_change = ObjectVar(model=ObjectChange)
|
|
1033
1100
|
|
|
1034
|
-
def run(self, object_change):
|
|
1101
|
+
def run(self, object_change): # pylint: disable=arguments-differ
|
|
1035
1102
|
"""JobHookReceiver subclasses generally shouldn't need to override this method."""
|
|
1036
1103
|
self.receive_job_hook(
|
|
1037
1104
|
change=object_change,
|
|
@@ -1060,7 +1127,7 @@ class JobButtonReceiver(Job):
|
|
|
1060
1127
|
object_pk = StringVar()
|
|
1061
1128
|
object_model_name = StringVar()
|
|
1062
1129
|
|
|
1063
|
-
def run(self, object_pk, object_model_name):
|
|
1130
|
+
def run(self, object_pk, object_model_name): # pylint: disable=arguments-differ
|
|
1064
1131
|
"""JobButtonReceiver subclasses generally shouldn't need to override this method."""
|
|
1065
1132
|
model = get_model_from_name(object_model_name)
|
|
1066
1133
|
obj = model.objects.get(pk=object_pk)
|
nautobot/extras/managers.py
CHANGED
|
@@ -22,8 +22,9 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
|
|
|
22
22
|
return self.model(id=task_id)
|
|
23
23
|
|
|
24
24
|
@transaction_retry(max_retries=2)
|
|
25
|
-
def store_result(
|
|
25
|
+
def store_result( # pylint:disable=arguments-differ # Nautobot adds kwargs like job_model_id and scheduled_job_id
|
|
26
26
|
self,
|
|
27
|
+
*,
|
|
27
28
|
task_id,
|
|
28
29
|
result,
|
|
29
30
|
status,
|
|
@@ -41,6 +42,7 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
|
|
|
41
42
|
using=None,
|
|
42
43
|
content_type=None,
|
|
43
44
|
content_encoding=None,
|
|
45
|
+
**kwargs,
|
|
44
46
|
):
|
|
45
47
|
"""
|
|
46
48
|
Store the result and status of a Celery task.
|
|
@@ -2,7 +2,6 @@ from collections import OrderedDict
|
|
|
2
2
|
|
|
3
3
|
from django.db import migrations
|
|
4
4
|
|
|
5
|
-
from nautobot.extras.choices import LogLevelChoices
|
|
6
5
|
from nautobot.extras.constants import (
|
|
7
6
|
JOB_LOG_MAX_ABSOLUTE_URL_LENGTH,
|
|
8
7
|
JOB_LOG_MAX_GROUPING_LENGTH,
|
|
@@ -123,14 +122,13 @@ def reverse_migrate_params(apps, schema_editor):
|
|
|
123
122
|
]
|
|
124
123
|
)
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
job_result.data["total"][entry.log_level] += 1
|
|
125
|
+
job_result.data[entry.grouping].setdefault(entry.log_level, 0)
|
|
126
|
+
job_result.data[entry.grouping][entry.log_level] += 1
|
|
127
|
+
if "total" not in job_result.data:
|
|
128
|
+
job_result.data["total"] = _data_grouping_struct()
|
|
129
|
+
del job_result.data["total"]["log"]
|
|
130
|
+
job_result.data["total"].setdefault(entry.log_level, 0)
|
|
131
|
+
job_result.data["total"][entry.log_level] += 1
|
|
134
132
|
|
|
135
133
|
job_result.save()
|
|
136
134
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 4.2.16 on 2024-11-11 07:04
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("extras", "0119_remove_task_queues_from_job_and_queue_from_scheduled_job"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="job",
|
|
14
|
+
name="is_singleton",
|
|
15
|
+
field=models.BooleanField(default=False),
|
|
16
|
+
),
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name="job",
|
|
19
|
+
name="is_singleton_override",
|
|
20
|
+
field=models.BooleanField(default=False),
|
|
21
|
+
),
|
|
22
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 4.2.16 on 2024-12-03 15:32
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("extras", "0120_job_is_singleton_job_is_singleton_override"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AlterField(
|
|
13
|
+
model_name="team",
|
|
14
|
+
name="contacts",
|
|
15
|
+
field=models.ManyToManyField(blank=True, related_name="teams", to="extras.contact"),
|
|
16
|
+
),
|
|
17
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Generated by Django 4.2.17 on 2025-01-08 16:59
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
import django.db.models.deletion
|
|
5
|
+
|
|
6
|
+
import nautobot.extras.utils
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
dependencies = [
|
|
11
|
+
("contenttypes", "0002_remove_content_type_name"),
|
|
12
|
+
("extras", "0121_alter_team_contacts"),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AddField(
|
|
17
|
+
model_name="graphqlquery",
|
|
18
|
+
name="owner_content_type",
|
|
19
|
+
field=models.ForeignKey(
|
|
20
|
+
blank=True,
|
|
21
|
+
default=None,
|
|
22
|
+
limit_choices_to=nautobot.extras.utils.FeatureQuery("graphql_query_owners"),
|
|
23
|
+
null=True,
|
|
24
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
25
|
+
related_name="graphql_queries",
|
|
26
|
+
to="contenttypes.contenttype",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
migrations.AddField(
|
|
30
|
+
model_name="graphqlquery",
|
|
31
|
+
name="owner_object_id",
|
|
32
|
+
field=models.UUIDField(blank=True, default=None, null=True),
|
|
33
|
+
),
|
|
34
|
+
]
|
|
@@ -61,7 +61,7 @@ class Contact(ContactTeamSharedBase):
|
|
|
61
61
|
class Team(ContactTeamSharedBase):
|
|
62
62
|
"""A group of Contacts, usable interchangeably with a single Contact in most cases."""
|
|
63
63
|
|
|
64
|
-
contacts = models.ManyToManyField(to=Contact, related_name="teams")
|
|
64
|
+
contacts = models.ManyToManyField(to=Contact, related_name="teams", blank=True)
|
|
65
65
|
|
|
66
66
|
class Meta(ContactTeamSharedBase.Meta):
|
|
67
67
|
abstract = False
|
|
@@ -370,11 +370,11 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
|
370
370
|
|
|
371
371
|
def get_for_model(self, model, exclude_filter_disabled=False):
|
|
372
372
|
"""
|
|
373
|
-
Return all CustomFields assigned to the given model.
|
|
373
|
+
Return (and cache) all CustomFields assigned to the given model.
|
|
374
374
|
|
|
375
375
|
Args:
|
|
376
|
-
model: The django model to which custom fields are registered
|
|
377
|
-
exclude_filter_disabled: Exclude any custom fields which have filter logic disabled
|
|
376
|
+
model (Model): The django model to which custom fields are registered
|
|
377
|
+
exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
|
|
378
378
|
"""
|
|
379
379
|
concrete_model = model._meta.concrete_model
|
|
380
380
|
cache_key = (
|
|
@@ -521,6 +521,26 @@ class CustomField(
|
|
|
521
521
|
def __str__(self):
|
|
522
522
|
return self.label
|
|
523
523
|
|
|
524
|
+
@property
|
|
525
|
+
def choices_cache_key(self):
|
|
526
|
+
return f"nautobot.extras.customfield.choices.{self.pk}"
|
|
527
|
+
|
|
528
|
+
@property
|
|
529
|
+
def choices(self) -> list[str]:
|
|
530
|
+
"""
|
|
531
|
+
Cacheable shorthand for retrieving custom_field_choices values associated with this model.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
list[str]: List of choice values, ordered by weight.
|
|
535
|
+
"""
|
|
536
|
+
if self.type not in [CustomFieldTypeChoices.TYPE_SELECT, CustomFieldTypeChoices.TYPE_MULTISELECT]:
|
|
537
|
+
return []
|
|
538
|
+
choices = cache.get(self.choices_cache_key)
|
|
539
|
+
if choices is None:
|
|
540
|
+
choices = list(self.custom_field_choices.order_by("weight", "value").values_list("value", flat=True))
|
|
541
|
+
cache.set(self.choices_cache_key, choices)
|
|
542
|
+
return choices
|
|
543
|
+
|
|
524
544
|
def save(self, *args, **kwargs):
|
|
525
545
|
self.clean()
|
|
526
546
|
super().save(*args, **kwargs)
|
|
@@ -578,7 +598,13 @@ class CustomField(
|
|
|
578
598
|
)
|
|
579
599
|
|
|
580
600
|
def to_form_field(
|
|
581
|
-
self,
|
|
601
|
+
self,
|
|
602
|
+
set_initial=True,
|
|
603
|
+
enforce_required=True,
|
|
604
|
+
for_csv_import=False,
|
|
605
|
+
simple_json_filter=False,
|
|
606
|
+
label=None,
|
|
607
|
+
for_filter_form=False,
|
|
582
608
|
):
|
|
583
609
|
"""
|
|
584
610
|
Return a form field suitable for setting a CustomField's value for an object.
|
|
@@ -590,6 +616,7 @@ class CustomField(
|
|
|
590
616
|
this is *not* used for CSV imports since 2.0, but it *is* used for JSON/YAML import of DeviceTypes.
|
|
591
617
|
simple_json_filter: Return a TextInput widget for JSON filtering instead of the default TextArea widget.
|
|
592
618
|
label: Set the input label manually (if required); otherwise, defaults to field's __str__() implementation.
|
|
619
|
+
for_filter_form: If True return the relevant form field for filter form
|
|
593
620
|
"""
|
|
594
621
|
initial = self.default if set_initial else None
|
|
595
622
|
required = self.required if enforce_required else False
|
|
@@ -672,12 +699,11 @@ class CustomField(
|
|
|
672
699
|
|
|
673
700
|
# Select or Multi-select
|
|
674
701
|
else:
|
|
675
|
-
choices = [(
|
|
676
|
-
default_choice = self.custom_field_choices.filter(value=self.default).first()
|
|
702
|
+
choices = [(value, value) for value in self.choices]
|
|
677
703
|
|
|
678
704
|
# Set the initial value to the first available choice (if any)
|
|
679
|
-
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
|
680
|
-
if not required or
|
|
705
|
+
if self.type == CustomFieldTypeChoices.TYPE_SELECT and not for_filter_form:
|
|
706
|
+
if not required or self.default not in self.choices:
|
|
681
707
|
choices = add_blank_choice(choices)
|
|
682
708
|
field_class = CSVChoiceField if for_csv_import else forms.ChoiceField
|
|
683
709
|
field = field_class(
|
|
@@ -704,16 +730,10 @@ class CustomField(
|
|
|
704
730
|
|
|
705
731
|
def to_filter_form_field(self, lookup_expr="exact", *args, **kwargs):
|
|
706
732
|
"""Return a filter form field suitable for filtering a CustomField's value for an object."""
|
|
707
|
-
form_field = self.to_form_field(*args, **kwargs)
|
|
708
|
-
# We would handle type selection differently because:
|
|
709
|
-
# 1. We'd need to use StaticSelect2Multiple for lookup_type 'exact' because self.type `select` uses StaticSelect2 by default.
|
|
710
|
-
# 2. Remove the blank choice since StaticSelect2Multiple is always blank and interprets the blank choice as an extra option.
|
|
711
|
-
# 3. If lookup_type is not the same as exact, use MultiValueCharInput
|
|
733
|
+
form_field = self.to_form_field(*args, **kwargs, for_filter_form=True)
|
|
734
|
+
# We would handle type selection differently because: If lookup_type is not the same as exact, use MultiValueCharInput
|
|
712
735
|
if self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
|
713
|
-
if lookup_expr in ["exact", "contains"]:
|
|
714
|
-
choices = form_field.choices[1:]
|
|
715
|
-
form_field.widget = StaticSelect2Multiple(choices=choices)
|
|
716
|
-
else:
|
|
736
|
+
if lookup_expr not in ["exact", "contains"]:
|
|
717
737
|
form_field.widget = MultiValueCharInput()
|
|
718
738
|
return form_field
|
|
719
739
|
|
|
@@ -778,17 +798,15 @@ class CustomField(
|
|
|
778
798
|
|
|
779
799
|
# Validate selected choice
|
|
780
800
|
elif self.type == CustomFieldTypeChoices.TYPE_SELECT:
|
|
781
|
-
if value not in self.
|
|
782
|
-
raise ValidationError(
|
|
783
|
-
f"Invalid choice ({value}). Available choices are: {', '.join(self.custom_field_choices.values_list('value', flat=True))}"
|
|
784
|
-
)
|
|
801
|
+
if value not in self.choices:
|
|
802
|
+
raise ValidationError(f"Invalid choice ({value}). Available choices are: {', '.join(self.choices)}")
|
|
785
803
|
|
|
786
804
|
elif self.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
|
787
805
|
if isinstance(value, str):
|
|
788
806
|
value = value.split(",")
|
|
789
|
-
if not set(value).issubset(self.
|
|
807
|
+
if not set(value).issubset(self.choices):
|
|
790
808
|
raise ValidationError(
|
|
791
|
-
f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.
|
|
809
|
+
f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.choices)}"
|
|
792
810
|
)
|
|
793
811
|
|
|
794
812
|
elif self.required:
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Models for representing external data sources."""
|
|
2
2
|
|
|
3
|
+
from contextlib import contextmanager
|
|
3
4
|
from importlib.util import find_spec
|
|
5
|
+
import logging
|
|
4
6
|
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import tempfile
|
|
5
9
|
|
|
6
10
|
from django.conf import settings
|
|
7
11
|
from django.core.exceptions import ValidationError
|
|
@@ -12,12 +16,16 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
|
12
16
|
from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
|
|
13
17
|
from nautobot.core.models.generics import PrimaryModel
|
|
14
18
|
from nautobot.core.models.validators import EnhancedURLValidator
|
|
19
|
+
from nautobot.core.utils.git import GitRepo
|
|
15
20
|
from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
|
|
16
21
|
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
17
24
|
|
|
18
25
|
@extras_features(
|
|
19
26
|
"config_context_owners",
|
|
20
27
|
"export_template_owners",
|
|
28
|
+
"graphql_query_owners",
|
|
21
29
|
"graphql",
|
|
22
30
|
"job_results",
|
|
23
31
|
"webhooks",
|
|
@@ -167,3 +175,80 @@ class GitRepository(PrimaryModel):
|
|
|
167
175
|
if dry_run:
|
|
168
176
|
return enqueue_git_repository_diff_origin_and_local(self, user)
|
|
169
177
|
return enqueue_pull_git_repository_and_refresh_data(self, user)
|
|
178
|
+
|
|
179
|
+
@contextmanager
|
|
180
|
+
def clone_to_directory_context(self, path=None, branch=None, head=None, depth=0):
|
|
181
|
+
"""
|
|
182
|
+
Context manager to perform a (shallow or full) clone of the Git repository in a temporary directory.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
|
|
186
|
+
branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
|
|
187
|
+
head (str, optional): Git commit hash to check out instead of pulling branch latest.
|
|
188
|
+
depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
if branch and head:
|
|
195
|
+
raise ValueError("Cannot specify both branch and head")
|
|
196
|
+
|
|
197
|
+
path_name = None
|
|
198
|
+
try:
|
|
199
|
+
path_name = self.clone_to_directory(path=path, branch=branch, head=head, depth=depth)
|
|
200
|
+
yield path_name
|
|
201
|
+
finally:
|
|
202
|
+
# Cleanup the temporary directory
|
|
203
|
+
if path_name:
|
|
204
|
+
self.cleanup_cloned_directory(path_name)
|
|
205
|
+
|
|
206
|
+
def clone_to_directory(self, path=None, branch=None, head=None, depth=0):
|
|
207
|
+
"""
|
|
208
|
+
Perform a (shallow or full) clone of the Git repository in a temporary directory.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
path (str, optional): The absolute directory path to clone into. If not specified, `tempfile.gettempdir()` will be used.
|
|
212
|
+
branch (str, optional): The branch to checkout. If not set, the GitRepository.branch will be used.
|
|
213
|
+
head (str, optional): Git commit hash to check out instead of pulling branch latest.
|
|
214
|
+
depth (int, optional): The depth of the clone. If set to 0, a full clone will be performed.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Returns the absolute path of the cloned repo if clone was successful, otherwise returns None.
|
|
218
|
+
"""
|
|
219
|
+
if branch and head:
|
|
220
|
+
raise ValueError("Cannot specify both branch and head")
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
path_name = tempfile.mkdtemp(dir=path, prefix=self.slug)
|
|
224
|
+
except PermissionError as e:
|
|
225
|
+
logger.error(f"Failed to create temporary directory at {path}: {e}")
|
|
226
|
+
raise e
|
|
227
|
+
|
|
228
|
+
if not branch:
|
|
229
|
+
branch = self.branch
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
repo_helper = GitRepo(path_name, self.remote_url, depth=depth, branch=branch)
|
|
233
|
+
if head:
|
|
234
|
+
repo_helper.checkout(branch, head)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.error(f"Failed to clone repository {self.name} to {path_name}: {e}")
|
|
237
|
+
raise e
|
|
238
|
+
|
|
239
|
+
logger.info(f"Cloned repository {self.name} to {path_name}")
|
|
240
|
+
return path_name
|
|
241
|
+
|
|
242
|
+
def cleanup_cloned_directory(self, path):
|
|
243
|
+
"""
|
|
244
|
+
Cleanup the cloned directory.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
path (str): The absolute directory path to cleanup.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
shutil.rmtree(path)
|
|
252
|
+
except OSError as os_error:
|
|
253
|
+
# log error if the cleanup fails
|
|
254
|
+
logger.error(f"Failed to cleanup temporary directory at {path}: {os_error}")
|
nautobot/extras/models/groups.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Dynamic Groups Models."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from typing import Optional
|
|
4
5
|
|
|
5
6
|
from django import forms
|
|
6
7
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
@@ -116,7 +117,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
116
117
|
return self._model
|
|
117
118
|
|
|
118
119
|
@property
|
|
119
|
-
def filterset_class(self):
|
|
120
|
+
def filterset_class(self) -> Optional[type[django_filters.FilterSet]]:
|
|
120
121
|
if getattr(self, "_filterset_class", None) is None:
|
|
121
122
|
try:
|
|
122
123
|
self._filterset_class = get_filterset_for_model(self.model)
|
|
@@ -125,7 +126,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
125
126
|
return self._filterset_class
|
|
126
127
|
|
|
127
128
|
@property
|
|
128
|
-
def filterform_class(self):
|
|
129
|
+
def filterform_class(self) -> Optional[type[forms.Form]]:
|
|
129
130
|
if getattr(self, "_filterform_class", None) is None:
|
|
130
131
|
try:
|
|
131
132
|
self._filterform_class = get_form_for_model(self.model, form_prefix="Filter")
|
|
@@ -134,7 +135,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
134
135
|
return self._filterform_class
|
|
135
136
|
|
|
136
137
|
@property
|
|
137
|
-
def form_class(self):
|
|
138
|
+
def form_class(self) -> Optional[type[forms.Form]]:
|
|
138
139
|
if getattr(self, "_form_class", None) is None:
|
|
139
140
|
try:
|
|
140
141
|
self._form_class = get_form_for_model(self.model)
|
|
@@ -147,19 +148,19 @@ class DynamicGroup(PrimaryModel):
|
|
|
147
148
|
"""Return all FilterForm fields in a dictionary."""
|
|
148
149
|
|
|
149
150
|
# Fail gracefully with an empty dict if nothing is working yet.
|
|
150
|
-
if
|
|
151
|
+
if self.form_class is None or self.filterform_class is None or self.filterset_class is None:
|
|
151
152
|
return {}
|
|
152
153
|
|
|
153
154
|
# Get model form and fields
|
|
154
|
-
modelform = self.form_class()
|
|
155
|
+
modelform = self.form_class() # pylint: disable=not-callable
|
|
155
156
|
modelform_fields = modelform.fields
|
|
156
157
|
|
|
157
158
|
# Get filter form and fields
|
|
158
|
-
filterform = self.filterform_class()
|
|
159
|
+
filterform = self.filterform_class() # pylint: disable=not-callable
|
|
159
160
|
filterform_fields = filterform.fields
|
|
160
161
|
|
|
161
162
|
# Get filterset and fields
|
|
162
|
-
filterset = self.filterset_class()
|
|
163
|
+
filterset = self.filterset_class() # pylint: disable=not-callable
|
|
163
164
|
filterset_fields = filterset.filters
|
|
164
165
|
|
|
165
166
|
# Get dynamic group filter field mappings (if any)
|
|
@@ -303,6 +304,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
303
304
|
# Since associated_object is a GenericForeignKey, we can't just do:
|
|
304
305
|
# return self.static_group_associations.values_list("associated_object", flat=True)
|
|
305
306
|
return self.model.objects.filter(
|
|
307
|
+
# pylint: disable=no-member # false positive about self.static_group_associations
|
|
306
308
|
pk__in=self.static_group_associations(manager="all_objects").values_list("associated_object_id", flat=True)
|
|
307
309
|
)
|
|
308
310
|
|
|
@@ -611,7 +613,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
611
613
|
raise ValidationError({"filter": "Filter can only be set for groups of type `dynamic-filter`."})
|
|
612
614
|
else:
|
|
613
615
|
# Validate against the filterset's internal form validation.
|
|
614
|
-
filterset = self.filterset_class(self.filter)
|
|
616
|
+
filterset = self.filterset_class(self.filter) # pylint: disable=not-callable
|
|
615
617
|
if not filterset.is_valid():
|
|
616
618
|
raise ValidationError(filterset.errors)
|
|
617
619
|
|
|
@@ -725,7 +727,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
725
727
|
if self.group_type != DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER:
|
|
726
728
|
raise RuntimeError(f"{self} is not a dynamic-filter group")
|
|
727
729
|
|
|
728
|
-
filterset = self.filterset_class(self.filter, self.model.objects.all())
|
|
730
|
+
filterset = self.filterset_class(self.filter, self.model.objects.all()) # pylint: disable=not-callable
|
|
729
731
|
query = models.Q()
|
|
730
732
|
|
|
731
733
|
# In this case we want all filters for a group's filter dict in a set intersection (boolean
|