nautobot 2.4.13__py3-none-any.whl → 2.4.15__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/choices.py +8 -0
- nautobot/apps/ui.py +14 -0
- nautobot/core/api/views.py +2 -0
- nautobot/core/choices.py +4 -0
- nautobot/core/filters.py +21 -41
- nautobot/core/graphql/generators.py +8 -0
- nautobot/core/graphql/schema.py +30 -30
- nautobot/core/management/commands/check_job_approval_status.py +47 -0
- nautobot/core/management/commands/generate_test_data.py +1 -1
- nautobot/core/management/commands/migrate.py +90 -1
- nautobot/core/models/tree_queries.py +17 -0
- nautobot/core/settings.py +2 -2
- nautobot/core/settings.yaml +3 -3
- nautobot/core/tables.py +29 -6
- nautobot/core/templates/base_django.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/generic/object_list.html +17 -20
- nautobot/core/templates/inc/breadcrumbs.html +14 -0
- nautobot/core/templatetags/buttons.py +2 -4
- nautobot/core/templatetags/helpers.py +29 -6
- nautobot/core/templatetags/ui_framework.py +21 -0
- nautobot/core/testing/api.py +7 -0
- nautobot/core/testing/filters.py +20 -3
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/tests/integration/test_filters.py +2 -2
- nautobot/core/tests/test_breadcrumbs.py +366 -0
- nautobot/core/tests/test_commands.py +40 -0
- nautobot/core/tests/test_filters.py +51 -1
- nautobot/core/tests/test_forms.py +1 -1
- nautobot/core/tests/test_graphql.py +4 -4
- nautobot/core/tests/test_titles.py +183 -0
- nautobot/core/tests/test_tree_queries.py +30 -0
- nautobot/core/tests/test_views.py +2 -2
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +538 -0
- nautobot/core/ui/bulk_buttons.py +53 -0
- nautobot/core/ui/object_detail.py +31 -8
- nautobot/core/ui/titles.py +127 -0
- nautobot/core/ui/utils.py +25 -0
- nautobot/core/utils/migrations.py +1 -1
- nautobot/core/views/__init__.py +1 -1
- nautobot/core/views/mixins.py +26 -1
- nautobot/core/views/renderers.py +20 -2
- nautobot/core/views/utils.py +14 -13
- nautobot/dcim/api/serializers.py +9 -0
- nautobot/dcim/choices.py +55 -0
- nautobot/dcim/constants.py +0 -16
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filters/__init__.py +15 -3
- nautobot/dcim/forms.py +120 -7
- nautobot/dcim/management/commands/trace_paths.py +1 -1
- nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
- nautobot/dcim/models/device_component_templates.py +8 -0
- nautobot/dcim/models/device_components.py +31 -12
- nautobot/dcim/models/devices.py +1 -1
- nautobot/dcim/models/power.py +171 -10
- nautobot/dcim/models/racks.py +7 -4
- nautobot/dcim/tables/devices.py +2 -0
- nautobot/dcim/tables/devicetypes.py +1 -0
- nautobot/dcim/tables/power.py +30 -2
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
- nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
- nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/rack.html +2 -318
- nautobot/dcim/templates/dcim/rack_edit.html +2 -47
- nautobot/dcim/templates/dcim/rack_retrieve.html +318 -0
- nautobot/dcim/templates/dcim/rack_update.html +47 -0
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
- nautobot/dcim/tests/test_api.py +24 -4
- nautobot/dcim/tests/test_filters.py +91 -13
- nautobot/dcim/tests/test_models.py +262 -0
- nautobot/dcim/tests/test_views.py +20 -12
- nautobot/dcim/urls.py +2 -27
- nautobot/dcim/utils.py +13 -30
- nautobot/dcim/views.py +428 -146
- nautobot/extras/choices.py +12 -4
- nautobot/extras/factory.py +19 -20
- nautobot/extras/filters/__init__.py +3 -2
- nautobot/extras/filters/mixins.py +23 -7
- nautobot/extras/forms/__init__.py +2 -1
- nautobot/extras/forms/forms.py +71 -0
- nautobot/extras/forms/mixins.py +4 -2
- nautobot/extras/managers.py +4 -1
- nautobot/extras/migrations/0062_collect_roles_from_related_apps_roles.py +30 -7
- nautobot/extras/migrations/0124_add_joblogentry_index.py +16 -0
- nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
- nautobot/extras/models/customfields.py +53 -5
- nautobot/extras/models/datasources.py +1 -2
- nautobot/extras/models/jobs.py +13 -3
- nautobot/extras/models/relationships.py +55 -6
- nautobot/extras/plugins/views.py +24 -1
- nautobot/extras/secrets/__init__.py +1 -1
- nautobot/extras/tables.py +9 -0
- nautobot/extras/templates/extras/customfield.html +2 -129
- nautobot/extras/templates/extras/customfield_edit.html +2 -108
- nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
- nautobot/extras/templates/extras/customfield_update.html +108 -0
- nautobot/extras/templates/extras/graphqlquery.html +2 -97
- nautobot/extras/templates/extras/graphqlquery_list.html +1 -0
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +97 -0
- nautobot/extras/templates/extras/inc/jobresult.html +7 -3
- nautobot/extras/templates/extras/jobresult.html +2 -155
- nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
- nautobot/extras/templates/extras/marketplace.html +5 -6
- nautobot/extras/templates/extras/note.html +2 -53
- nautobot/extras/templates/extras/note_retrieve.html +53 -0
- nautobot/extras/templates/extras/plugins_list.html +5 -6
- nautobot/extras/templates/extras/secretsgroup.html +2 -29
- nautobot/extras/templates/extras/secretsgroup_edit.html +2 -82
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +29 -0
- nautobot/extras/templates/extras/secretsgroup_update.html +82 -0
- nautobot/extras/templatetags/custom_links.py +2 -2
- nautobot/extras/templatetags/job_buttons.py +1 -1
- nautobot/extras/templatetags/plugins.py +1 -1
- nautobot/extras/tests/integration/test_computedfields.py +2 -2
- nautobot/extras/tests/integration/test_customfields.py +14 -11
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_notes.py +1 -1
- nautobot/extras/tests/integration/test_plugins.py +6 -6
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_customfields.py +115 -7
- nautobot/extras/tests/test_filters.py +9 -0
- nautobot/extras/tests/test_forms.py +2 -2
- nautobot/extras/tests/test_plugins.py +2 -3
- nautobot/extras/tests/test_relationships.py +14 -8
- nautobot/extras/tests/test_views.py +285 -2
- nautobot/extras/urls.py +5 -110
- nautobot/extras/utils.py +5 -2
- nautobot/extras/views.py +116 -311
- nautobot/ipam/api/views.py +69 -6
- nautobot/ipam/tables.py +8 -15
- nautobot/ipam/tests/migration/test_migrations.py +8 -8
- nautobot/ipam/tests/test_api.py +352 -2
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/project-static/docs/404.html +34 -34
- nautobot/project-static/docs/apps/index.html +34 -34
- nautobot/project-static/docs/apps/nautobot-apps.html +34 -34
- nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
- nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js → bundle.50899def.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.56ea9cef.min.js.map → bundle.50899def.min.js.map} +2 -2
- nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css → main.7e37652d.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.342714a4.min.css.map → main.7e37652d.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +39 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +36 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +139 -54
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +48 -38
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +50 -40
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +36 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +35 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +43 -39
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +52 -42
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +50 -41
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +54 -44
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +85 -93
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +154 -62
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +54 -46
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +146 -87
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +240 -70
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +38 -35
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +41 -35
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +173 -52
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +269 -85
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2643
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +36 -34
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +165 -89
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +161 -69
- nautobot/project-static/docs/development/apps/api/configuration-view.html +34 -34
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +34 -34
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +34 -34
- nautobot/project-static/docs/development/apps/api/models/global-search.html +34 -34
- nautobot/project-static/docs/development/apps/api/models/graphql.html +34 -34
- nautobot/project-static/docs/development/apps/api/models/index.html +34 -34
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +34 -34
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +34 -34
- nautobot/project-static/docs/development/apps/api/prometheus.html +34 -34
- nautobot/project-static/docs/development/apps/api/setup.html +34 -34
- nautobot/project-static/docs/development/apps/api/testing.html +34 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +34 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +34 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +34 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +34 -34
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/base-template.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/index.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +42 -36
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/notes.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +34 -34
- nautobot/project-static/docs/development/apps/api/views/urls.html +34 -34
- nautobot/project-static/docs/development/apps/index.html +34 -34
- nautobot/project-static/docs/development/apps/migration/code-updates.html +34 -34
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +34 -34
- nautobot/project-static/docs/development/apps/migration/from-v1.html +34 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +34 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +34 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +34 -34
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +34 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +37 -37
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +34 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +34 -34
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +34 -34
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +37 -37
- nautobot/project-static/docs/development/core/application-registry.html +162 -133
- nautobot/project-static/docs/development/core/best-practices.html +34 -34
- nautobot/project-static/docs/development/core/bootstrap-ui.html +34 -34
- nautobot/project-static/docs/development/core/caching.html +34 -34
- nautobot/project-static/docs/development/core/controllers.html +34 -34
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +34 -34
- nautobot/project-static/docs/development/core/generic-views.html +34 -34
- nautobot/project-static/docs/development/core/getting-started.html +34 -34
- nautobot/project-static/docs/development/core/homepage.html +34 -34
- nautobot/project-static/docs/development/core/index.html +34 -34
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +34 -34
- nautobot/project-static/docs/development/core/model-checklist.html +34 -34
- nautobot/project-static/docs/development/core/model-features.html +34 -34
- nautobot/project-static/docs/development/core/natural-keys.html +34 -34
- nautobot/project-static/docs/development/core/navigation-menu.html +34 -34
- nautobot/project-static/docs/development/core/release-checklist.html +34 -34
- nautobot/project-static/docs/development/core/role-internals.html +34 -34
- nautobot/project-static/docs/development/core/settings.html +34 -34
- nautobot/project-static/docs/development/core/style-guide.html +34 -34
- nautobot/project-static/docs/development/core/templates.html +34 -34
- nautobot/project-static/docs/development/core/testing.html +34 -34
- nautobot/project-static/docs/development/core/ui-component-framework.html +724 -289
- nautobot/project-static/docs/development/core/user-preferences.html +34 -34
- nautobot/project-static/docs/development/index.html +34 -34
- nautobot/project-static/docs/development/jobs/getting-started.html +34 -34
- nautobot/project-static/docs/development/jobs/index.html +34 -34
- nautobot/project-static/docs/development/jobs/installation.html +34 -34
- nautobot/project-static/docs/development/jobs/job-extensions.html +34 -34
- nautobot/project-static/docs/development/jobs/job-logging.html +34 -34
- nautobot/project-static/docs/development/jobs/job-patterns.html +34 -34
- nautobot/project-static/docs/development/jobs/job-structure.html +34 -34
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +34 -34
- nautobot/project-static/docs/development/jobs/testing.html +34 -34
- nautobot/project-static/docs/index.html +34 -34
- nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
- nautobot/project-static/docs/media/power_distribution.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +34 -34
- nautobot/project-static/docs/overview/design_philosophy.html +34 -34
- nautobot/project-static/docs/release-notes/index.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.0.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.1.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.2.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.3.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.4.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.5.html +34 -34
- nautobot/project-static/docs/release-notes/version-1.6.html +34 -34
- nautobot/project-static/docs/release-notes/version-2.0.html +34 -34
- nautobot/project-static/docs/release-notes/version-2.1.html +34 -34
- nautobot/project-static/docs/release-notes/version-2.2.html +34 -34
- nautobot/project-static/docs/release-notes/version-2.3.html +34 -34
- nautobot/project-static/docs/release-notes/version-2.4.html +402 -34
- nautobot/project-static/docs/requirements.txt +3 -3
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +303 -299
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +34 -34
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +34 -34
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +34 -34
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +34 -34
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +34 -34
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +37 -37
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +34 -34
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/index.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +34 -34
- nautobot/project-static/docs/user-guide/administration/installation/services.html +34 -34
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +34 -34
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +34 -34
- nautobot/project-static/docs/user-guide/administration/security/index.html +34 -34
- nautobot/project-static/docs/user-guide/administration/security/notices.html +34 -34
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +296 -251
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +34 -34
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +37 -37
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +42 -52
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +37 -37
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +316 -39
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +35 -35
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +147 -37
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +52 -35
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +51 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +34 -34
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +34 -34
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +34 -34
- nautobot/project-static/docs/user-guide/index.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +35 -35
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +34 -34
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +34 -34
- nautobot/tenancy/api/views.py +2 -1
- nautobot/users/tests/test_api.py +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
- nautobot/virtualization/urls.py +3 -61
- nautobot/virtualization/views.py +48 -72
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/METADATA +27 -27
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/RECORD +463 -439
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/NOTICE +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/WHEEL +0 -0
- {nautobot-2.4.13.dist-info → nautobot-2.4.15.dist-info}/entry_points.txt +0 -0
nautobot/extras/choices.py
CHANGED
|
@@ -483,22 +483,30 @@ class SecretsGroupAccessTypeChoices(ChoiceSet):
|
|
|
483
483
|
|
|
484
484
|
|
|
485
485
|
class SecretsGroupSecretTypeChoices(ChoiceSet):
|
|
486
|
+
TYPE_AUTHKEY = "authentication-key"
|
|
487
|
+
TYPE_AUTHPROTOCOL = "authentication-protocol"
|
|
486
488
|
TYPE_KEY = "key"
|
|
489
|
+
TYPE_NOTES = "notes"
|
|
487
490
|
TYPE_PASSWORD = "password" # noqa: S105 # hardcoded-password-string -- false positive
|
|
491
|
+
TYPE_PRIVALGORITHM = "private-algorithm"
|
|
492
|
+
TYPE_PRIVKEY = "private-key"
|
|
488
493
|
TYPE_SECRET = "secret" # noqa: S105 # hardcoded-password-string -- false positive
|
|
489
494
|
TYPE_TOKEN = "token" # noqa: S105 # hardcoded-password-string -- false positive
|
|
490
|
-
TYPE_USERNAME = "username"
|
|
491
495
|
TYPE_URL = "url"
|
|
492
|
-
|
|
496
|
+
TYPE_USERNAME = "username"
|
|
493
497
|
|
|
494
498
|
CHOICES = (
|
|
499
|
+
(TYPE_AUTHKEY, "Authentication Key"),
|
|
500
|
+
(TYPE_AUTHPROTOCOL, "Authentication Protocol"),
|
|
495
501
|
(TYPE_KEY, "Key"),
|
|
502
|
+
(TYPE_NOTES, "Notes"),
|
|
496
503
|
(TYPE_PASSWORD, "Password"),
|
|
504
|
+
(TYPE_PRIVALGORITHM, "Private Algorithm"),
|
|
505
|
+
(TYPE_PRIVKEY, "Private Key"),
|
|
497
506
|
(TYPE_SECRET, "Secret"),
|
|
498
507
|
(TYPE_TOKEN, "Token"),
|
|
499
|
-
(TYPE_USERNAME, "Username"),
|
|
500
508
|
(TYPE_URL, "URL"),
|
|
501
|
-
(
|
|
509
|
+
(TYPE_USERNAME, "Username"),
|
|
502
510
|
)
|
|
503
511
|
|
|
504
512
|
|
nautobot/extras/factory.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from datetime import
|
|
1
|
+
from datetime import timezone
|
|
2
2
|
import json
|
|
3
3
|
|
|
4
4
|
from django.contrib.auth import get_user_model
|
|
@@ -137,8 +137,10 @@ class JobLogEntryFactory(BaseModelFactory):
|
|
|
137
137
|
def created(self):
|
|
138
138
|
if self.job_result.date_done:
|
|
139
139
|
return faker.Faker().date_time_between_dates(
|
|
140
|
-
datetime_start=self.job_result.
|
|
140
|
+
datetime_start=self.job_result.date_started, datetime_end=self.job_result.date_done, tzinfo=timezone.utc
|
|
141
141
|
)
|
|
142
|
+
elif self.job_result.date_started:
|
|
143
|
+
return faker.Faker().past_datetime(start_date=self.job_result.date_started, tzinfo=timezone.utc)
|
|
142
144
|
return faker.Faker().past_datetime(start_date=self.job_result.date_created, tzinfo=timezone.utc)
|
|
143
145
|
|
|
144
146
|
|
|
@@ -183,7 +185,7 @@ class JobResultFactory(BaseModelFactory):
|
|
|
183
185
|
job_model = factory.Maybe("has_job_model", random_instance(Job), None)
|
|
184
186
|
name = factory.Faker("word")
|
|
185
187
|
task_name = factory.Faker("word")
|
|
186
|
-
# date_created and date_done are handled below
|
|
188
|
+
# date_created, date_started, and date_done are handled below
|
|
187
189
|
user = factory.Maybe("has_user", random_instance(get_user_model()), None)
|
|
188
190
|
status = factory.Iterator(
|
|
189
191
|
[
|
|
@@ -210,23 +212,20 @@ class JobResultFactory(BaseModelFactory):
|
|
|
210
212
|
return None
|
|
211
213
|
|
|
212
214
|
@factory.post_generation
|
|
213
|
-
def
|
|
214
|
-
if created:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
else:
|
|
228
|
-
# TODO, should we create "in progress" job results without a date_done value as well?
|
|
229
|
-
self.date_done = self.date_created + timedelta(minutes=faker.Faker().random_int())
|
|
215
|
+
def dates(self, created, extracted, **kwargs): # pylint: disable=method-hidden
|
|
216
|
+
if not created:
|
|
217
|
+
return
|
|
218
|
+
if extracted:
|
|
219
|
+
return
|
|
220
|
+
# Create a date_created in the past, but not too far in the past
|
|
221
|
+
self.date_created = faker.Faker().date_time_between(start_date="-1y", end_date="-1w", tzinfo=timezone.utc)
|
|
222
|
+
self.date_started = faker.Faker().date_time_between(
|
|
223
|
+
start_date=self.date_created, end_date="-1d", tzinfo=timezone.utc
|
|
224
|
+
)
|
|
225
|
+
# TODO, should we create "in progress" job results without a date_done value as well?
|
|
226
|
+
self.date_done = faker.Faker().date_time_between(
|
|
227
|
+
start_date=self.date_started, end_date="now", tzinfo=timezone.utc
|
|
228
|
+
)
|
|
230
229
|
|
|
231
230
|
|
|
232
231
|
class MetadataChoiceFactory(BaseModelFactory):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from difflib import get_close_matches
|
|
2
|
+
from uuid import UUID
|
|
2
3
|
|
|
3
4
|
from django.conf import settings
|
|
4
5
|
from django.contrib.auth import get_user_model
|
|
@@ -1012,7 +1013,7 @@ class JobResultFilterSet(BaseFilterSet, CustomFieldModelFilterSetMixin):
|
|
|
1012
1013
|
|
|
1013
1014
|
class Meta:
|
|
1014
1015
|
model = JobResult
|
|
1015
|
-
fields = ["id", "date_created", "date_done", "name", "status", "user", "scheduled_job"]
|
|
1016
|
+
fields = ["id", "date_created", "date_started", "date_done", "name", "status", "user", "scheduled_job"]
|
|
1016
1017
|
|
|
1017
1018
|
|
|
1018
1019
|
class JobLogEntryFilterSet(BaseFilterSet):
|
|
@@ -1193,7 +1194,7 @@ class NoteFilterSet(BaseFilterSet):
|
|
|
1193
1194
|
filter_predicates={
|
|
1194
1195
|
"user_name": "icontains",
|
|
1195
1196
|
"note": "icontains",
|
|
1196
|
-
"assigned_object_id": "exact",
|
|
1197
|
+
"assigned_object_id": {"lookup_expr": "exact", "preprocessor": UUID},
|
|
1197
1198
|
},
|
|
1198
1199
|
)
|
|
1199
1200
|
assigned_object_type = ContentTypeFilter()
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
2
|
from django.db.models import Model, Q
|
|
3
|
+
from django.utils.encoding import force_str
|
|
4
|
+
from django.utils.text import capfirst
|
|
3
5
|
import django_filters
|
|
4
6
|
from django_filters.constants import EMPTY_VALUES
|
|
5
7
|
from django_filters.utils import verbose_lookup_expr
|
|
@@ -74,7 +76,9 @@ class CustomFieldModelFilterSetMixin(django_filters.FilterSet):
|
|
|
74
76
|
CustomFieldTypeChoices.TYPE_SELECT: CustomFieldSelectFilter,
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
custom_fields = CustomField.objects.get_for_model(
|
|
79
|
+
custom_fields = CustomField.objects.get_for_model(
|
|
80
|
+
self._meta.model, exclude_filter_disabled=True, get_queryset=False
|
|
81
|
+
)
|
|
78
82
|
for cf in custom_fields:
|
|
79
83
|
# Determine filter class for this CustomField type, default to CustomFieldCharFilter
|
|
80
84
|
new_filter_name = cf.add_prefix_to_cf_key()
|
|
@@ -129,11 +133,21 @@ class CustomFieldModelFilterSetMixin(django_filters.FilterSet):
|
|
|
129
133
|
# Create new filters for each lookup expression in the map
|
|
130
134
|
for lookup_name, lookup_expr in lookup_map.items():
|
|
131
135
|
new_filter_name = f"{filter_name}__{lookup_name}"
|
|
136
|
+
|
|
137
|
+
# Based on logic in BaseFilterSet._generate_lookup_expression_filters
|
|
138
|
+
verbose_expression = (
|
|
139
|
+
["exclude", custom_field.label] if lookup_name.startswith("n") else [custom_field.label]
|
|
140
|
+
)
|
|
141
|
+
if isinstance(lookup_expr, str):
|
|
142
|
+
verbose_expression.append(verbose_lookup_expr(lookup_expr))
|
|
143
|
+
verbose_expression = [force_str(part) for part in verbose_expression if part]
|
|
144
|
+
label = capfirst(" ".join(verbose_expression))
|
|
145
|
+
|
|
132
146
|
new_filter = filter_type(
|
|
133
147
|
field_name=custom_field.key,
|
|
134
148
|
lookup_expr=lookup_expr,
|
|
135
149
|
custom_field=custom_field,
|
|
136
|
-
label=
|
|
150
|
+
label=label,
|
|
137
151
|
exclude=lookup_name.startswith("n"),
|
|
138
152
|
)
|
|
139
153
|
|
|
@@ -220,13 +234,13 @@ class RelationshipModelFilterSetMixin(django_filters.FilterSet):
|
|
|
220
234
|
"""
|
|
221
235
|
Append form fields for all Relationships assigned to this model.
|
|
222
236
|
"""
|
|
223
|
-
src_relationships, dst_relationships = Relationship.objects.get_for_model(
|
|
237
|
+
src_relationships, dst_relationships = Relationship.objects.get_for_model(
|
|
238
|
+
model=model, hidden=False, get_queryset=False
|
|
239
|
+
)
|
|
224
240
|
|
|
225
|
-
|
|
226
|
-
self._append_relationships_side([rel], RelationshipSideChoices.SIDE_SOURCE, model)
|
|
241
|
+
self._append_relationships_side(src_relationships, RelationshipSideChoices.SIDE_SOURCE, model)
|
|
227
242
|
|
|
228
|
-
|
|
229
|
-
self._append_relationships_side([rel], RelationshipSideChoices.SIDE_DESTINATION, model)
|
|
243
|
+
self._append_relationships_side(dst_relationships, RelationshipSideChoices.SIDE_DESTINATION, model)
|
|
230
244
|
|
|
231
245
|
def _append_relationships_side(self, relationships, initial_side, model):
|
|
232
246
|
"""
|
|
@@ -295,6 +309,7 @@ class RoleModelFilterSetMixin(django_filters.FilterSet):
|
|
|
295
309
|
field_name="role",
|
|
296
310
|
query_params={"content_types": [cls._meta.model._meta.label_lower]},
|
|
297
311
|
)
|
|
312
|
+
cls.declared_filters["role"] = filters["role"] # pylint: disable=no-member
|
|
298
313
|
|
|
299
314
|
return filters
|
|
300
315
|
|
|
@@ -324,5 +339,6 @@ class StatusModelFilterSetMixin(django_filters.FilterSet):
|
|
|
324
339
|
field_name="status",
|
|
325
340
|
query_params={"content_types": [cls._meta.model._meta.label_lower]},
|
|
326
341
|
)
|
|
342
|
+
cls.declared_filters["status"] = filters["status"] # pylint: disable=no-member
|
|
327
343
|
|
|
328
344
|
return filters
|
|
@@ -12,5 +12,6 @@ from .contacts import (
|
|
|
12
12
|
TeamFilterForm,
|
|
13
13
|
TeamForm,
|
|
14
14
|
)
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
from .mixins import * # noqa: F403 # undefined-local-with-import-star
|
|
17
|
+
from .forms import * # noqa: F403 # undefined-local-with-import-star
|
nautobot/extras/forms/forms.py
CHANGED
|
@@ -43,6 +43,7 @@ from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
|
|
|
43
43
|
from nautobot.dcim.models import Device, DeviceRedundancyGroup, DeviceType, Location, Platform
|
|
44
44
|
from nautobot.extras.choices import (
|
|
45
45
|
ButtonClassChoices,
|
|
46
|
+
CustomFieldFilterLogicChoices,
|
|
46
47
|
DynamicGroupTypeChoices,
|
|
47
48
|
JobExecutionType,
|
|
48
49
|
JobQueueTypeChoices,
|
|
@@ -134,6 +135,7 @@ __all__ = (
|
|
|
134
135
|
"ConfigContextSchemaForm",
|
|
135
136
|
"CustomFieldBulkCreateForm", # 2.0 TODO remove this deprecated class
|
|
136
137
|
"CustomFieldBulkDeleteForm",
|
|
138
|
+
"CustomFieldBulkEditForm",
|
|
137
139
|
"CustomFieldChoiceFormSet",
|
|
138
140
|
"CustomFieldFilterForm",
|
|
139
141
|
"CustomFieldForm",
|
|
@@ -197,6 +199,7 @@ __all__ = (
|
|
|
197
199
|
"SecretFilterForm",
|
|
198
200
|
"SecretForm",
|
|
199
201
|
"SecretsGroupAssociationFormSet",
|
|
202
|
+
"SecretsGroupBulkEditForm",
|
|
200
203
|
"SecretsGroupFilterForm",
|
|
201
204
|
"SecretsGroupForm",
|
|
202
205
|
"StaticGroupAssociationFilterForm",
|
|
@@ -464,6 +467,66 @@ class CustomFieldDescriptionField(CommentField):
|
|
|
464
467
|
return "Also used as the help text when editing models using this custom field.<br>" + super().default_helptext
|
|
465
468
|
|
|
466
469
|
|
|
470
|
+
class CustomFieldBulkEditForm(BootstrapMixin, NoteModelBulkEditFormMixin):
|
|
471
|
+
pk = forms.ModelMultipleChoiceField(queryset=CustomField.objects.all(), widget=forms.MultipleHiddenInput)
|
|
472
|
+
grouping = forms.CharField(
|
|
473
|
+
required=False,
|
|
474
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
475
|
+
label="Grouping",
|
|
476
|
+
help_text="Human-readable grouping that this custom field belongs to.",
|
|
477
|
+
)
|
|
478
|
+
description = forms.CharField(
|
|
479
|
+
required=False,
|
|
480
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
481
|
+
label="Description",
|
|
482
|
+
help_text="A helpful description for this field.",
|
|
483
|
+
)
|
|
484
|
+
required = forms.NullBooleanField(
|
|
485
|
+
required=False,
|
|
486
|
+
widget=BulkEditNullBooleanSelect,
|
|
487
|
+
label="Required",
|
|
488
|
+
help_text="If true, this field is required when creating new objects or editing an existing object.",
|
|
489
|
+
)
|
|
490
|
+
filter_logic = forms.ChoiceField(
|
|
491
|
+
required=False,
|
|
492
|
+
choices=add_blank_choice(CustomFieldFilterLogicChoices.CHOICES),
|
|
493
|
+
label="Filter logic",
|
|
494
|
+
help_text="Loose matches any instance of a given string; Exact matches the entire field.",
|
|
495
|
+
)
|
|
496
|
+
weight = forms.IntegerField(
|
|
497
|
+
required=False, label="Weight", help_text="Fields with higher weights appear lower in a form."
|
|
498
|
+
)
|
|
499
|
+
advanced_ui = forms.NullBooleanField(
|
|
500
|
+
required=False,
|
|
501
|
+
widget=BulkEditNullBooleanSelect,
|
|
502
|
+
label="Move to Advanced tab",
|
|
503
|
+
help_text="Hide this field from the object's primary information tab. It will appear in the 'Advanced' tab instead.",
|
|
504
|
+
)
|
|
505
|
+
add_content_types = MultipleContentTypeField(
|
|
506
|
+
limit_choices_to=FeatureQuery("custom_fields"), required=False, label="Add Content Types"
|
|
507
|
+
)
|
|
508
|
+
remove_content_types = MultipleContentTypeField(
|
|
509
|
+
limit_choices_to=FeatureQuery("custom_fields"), required=False, label="Remove Content Types"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
class Meta:
|
|
513
|
+
model = CustomField
|
|
514
|
+
fields = (
|
|
515
|
+
"grouping",
|
|
516
|
+
"description",
|
|
517
|
+
"required",
|
|
518
|
+
"filter_logic",
|
|
519
|
+
"weight",
|
|
520
|
+
"advanced_ui",
|
|
521
|
+
"add_content_types",
|
|
522
|
+
"remove_content_types",
|
|
523
|
+
)
|
|
524
|
+
nullable_fields = [
|
|
525
|
+
"grouping",
|
|
526
|
+
"description",
|
|
527
|
+
]
|
|
528
|
+
|
|
529
|
+
|
|
467
530
|
class CustomFieldForm(BootstrapMixin, forms.ModelForm):
|
|
468
531
|
label = forms.CharField(
|
|
469
532
|
required=True, max_length=CHARFIELD_MAX_LENGTH, help_text="Name of the field as displayed to users."
|
|
@@ -2082,6 +2145,14 @@ class RoleFilterForm(NautobotFilterForm):
|
|
|
2082
2145
|
#
|
|
2083
2146
|
|
|
2084
2147
|
|
|
2148
|
+
class SecretsGroupBulkEditForm(NautobotBulkEditForm):
|
|
2149
|
+
pk = forms.ModelMultipleChoiceField(queryset=SecretsGroup.objects.all(), widget=forms.MultipleHiddenInput())
|
|
2150
|
+
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
2151
|
+
|
|
2152
|
+
class Meta:
|
|
2153
|
+
model = SecretsGroup
|
|
2154
|
+
|
|
2155
|
+
|
|
2085
2156
|
def provider_choices():
|
|
2086
2157
|
return sorted([(slug, provider.name) for slug, provider in registry["secrets_providers"].items()])
|
|
2087
2158
|
|
nautobot/extras/forms/mixins.py
CHANGED
|
@@ -95,7 +95,7 @@ class CustomFieldModelFilterFormMixin(forms.Form):
|
|
|
95
95
|
def __init__(self, *args, **kwargs):
|
|
96
96
|
super().__init__(*args, **kwargs)
|
|
97
97
|
|
|
98
|
-
custom_fields = CustomField.objects.get_for_model(self.model, exclude_filter_disabled=True)
|
|
98
|
+
custom_fields = CustomField.objects.get_for_model(self.model, exclude_filter_disabled=True, get_queryset=False)
|
|
99
99
|
self.custom_fields = []
|
|
100
100
|
for cf in custom_fields:
|
|
101
101
|
field_name = cf.add_prefix_to_cf_key()
|
|
@@ -714,7 +714,9 @@ class RelationshipModelFilterFormMixin(forms.Form):
|
|
|
714
714
|
"""
|
|
715
715
|
Append form fields for all Relationships assigned to this model.
|
|
716
716
|
"""
|
|
717
|
-
src_relationships, dst_relationships = Relationship.objects.get_for_model(
|
|
717
|
+
src_relationships, dst_relationships = Relationship.objects.get_for_model(
|
|
718
|
+
model=self.model, hidden=False, get_queryset=False
|
|
719
|
+
)
|
|
718
720
|
|
|
719
721
|
for rel in src_relationships:
|
|
720
722
|
self._append_relationships_side([rel], RelationshipSideChoices.SIDE_SOURCE)
|
nautobot/extras/managers.py
CHANGED
|
@@ -111,10 +111,13 @@ class JobResultManager(BaseManager.from_queryset(RestrictedQuerySet), TaskResult
|
|
|
111
111
|
except Job.DoesNotExist:
|
|
112
112
|
pass
|
|
113
113
|
|
|
114
|
+
if "date_started" in kwargs:
|
|
115
|
+
fields["date_started"] = kwargs["date_started"]
|
|
116
|
+
|
|
114
117
|
obj, created = self.using(using).get_or_create(id=task_id, defaults=fields)
|
|
115
118
|
|
|
116
119
|
if not created:
|
|
117
|
-
# Make sure `date_done` is allowed to stay null until the task
|
|
120
|
+
# Make sure `date_done` is allowed to stay null until the task reaches a ready state.
|
|
118
121
|
#
|
|
119
122
|
# Default behavior in `django-celery-results` has this field as a
|
|
120
123
|
# `DateField(auto_now=True)` which just automatically updates the `date_done` field on every
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Generated by Django 3.2.16 on 2022-11-19 23:15
|
|
2
2
|
|
|
3
3
|
from collections import namedtuple
|
|
4
|
-
import logging
|
|
5
4
|
|
|
6
5
|
from django.db import migrations, models
|
|
7
6
|
|
|
@@ -24,8 +23,6 @@ color_map = {
|
|
|
24
23
|
"success": ColorChoices.COLOR_GREEN,
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
logger = logging.getLogger(__name__)
|
|
28
|
-
|
|
29
26
|
|
|
30
27
|
def create_equivalent_roles_of_virtualmachine_device_role(apps):
|
|
31
28
|
"""Create equivalent roles for the VirtualMachine DeviceRole."""
|
|
@@ -82,17 +79,43 @@ def create_roles(apps, roles_to_create, content_types):
|
|
|
82
79
|
"description": old_role.description,
|
|
83
80
|
"color": getattr(old_role, "color", color_map["default"]),
|
|
84
81
|
"weight": getattr(old_role, "weight", None),
|
|
82
|
+
"_custom_field_data": getattr(old_role, "_custom_field_data", {}),
|
|
85
83
|
},
|
|
86
84
|
)
|
|
87
85
|
if created:
|
|
88
|
-
|
|
86
|
+
print(f" Created Role {new_role.name!r}")
|
|
87
|
+
else:
|
|
88
|
+
updated = False
|
|
89
|
+
# Fix up in the case of overlapping roles
|
|
90
|
+
if new_role.weight is None and hasattr(old_role, "weight"):
|
|
91
|
+
new_role.weight = old_role.weight
|
|
92
|
+
updated = True
|
|
93
|
+
if old_role.description and not new_role.description:
|
|
94
|
+
new_role.description = old_role.description
|
|
95
|
+
updated = True
|
|
96
|
+
if hasattr(old_role, "_custom_field_data"):
|
|
97
|
+
for field, value in old_role._custom_field_data.items():
|
|
98
|
+
if field in new_role._custom_field_data:
|
|
99
|
+
if new_role._custom_field_data[field] != value:
|
|
100
|
+
print(
|
|
101
|
+
f" Value conflict for custom field {field!r} for role {new_role.name!r} - DATA LOSS"
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
new_role._custom_field_data[field] = value
|
|
105
|
+
updated = True
|
|
106
|
+
if updated:
|
|
107
|
+
print(f" Updated Role {new_role.name!r}")
|
|
108
|
+
new_role.save()
|
|
109
|
+
else:
|
|
110
|
+
print(f" No changes to already-created Role {new_role.name!r}")
|
|
111
|
+
|
|
89
112
|
if old_role_ct and hasattr(old_role, "pk"):
|
|
90
113
|
# Move over existing object change records to the new role we created
|
|
91
114
|
updated_count = ObjectChange.objects.filter(
|
|
92
115
|
changed_object_type=old_role_ct, changed_object_id=old_role.pk
|
|
93
116
|
).update(changed_object_type=new_role_ct, changed_object_id=new_role.pk)
|
|
94
|
-
|
|
95
|
-
f
|
|
117
|
+
print(
|
|
118
|
+
f" Updated {updated_count} ObjectChanges from the existing {old_role.name!r} {old_role._meta.label}"
|
|
96
119
|
)
|
|
97
120
|
|
|
98
121
|
# This is for all of the change records for roles which no longer exist
|
|
@@ -100,7 +123,7 @@ def create_roles(apps, roles_to_create, content_types):
|
|
|
100
123
|
updated_count = ObjectChange.objects.filter(changed_object_type=old_role_ct).update(
|
|
101
124
|
changed_object_type=new_role_ct
|
|
102
125
|
)
|
|
103
|
-
|
|
126
|
+
print(f" Updated {updated_count} leftover ObjectChanges from deleted {old_role_ct.model} records.")
|
|
104
127
|
|
|
105
128
|
roles = Role.objects.filter(name__in=[roles.name for roles in roles_to_create])
|
|
106
129
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-07-31 23:49
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("extras", "0123_alter_joblogentry_created"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddIndex(
|
|
13
|
+
model_name="joblogentry",
|
|
14
|
+
index=models.Index(fields=["job_result", "created"], name="extras_joblog_jr_created_idx"),
|
|
15
|
+
),
|
|
16
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 4.2.23 on 2025-08-11 16:18
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("django_celery_results", "0012_taskresult_date_started"),
|
|
9
|
+
("extras", "0124_add_joblogentry_index"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="jobresult",
|
|
15
|
+
name="date_started",
|
|
16
|
+
field=models.DateTimeField(blank=True, db_index=True, null=True),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from collections import OrderedDict
|
|
1
|
+
from collections import defaultdict, OrderedDict
|
|
2
2
|
from datetime import date, datetime
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
@@ -48,21 +48,42 @@ logger = logging.getLogger(__name__)
|
|
|
48
48
|
class ComputedFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
49
49
|
use_in_migrations = True
|
|
50
50
|
|
|
51
|
-
def get_for_model(self, model):
|
|
51
|
+
def get_for_model(self, model, get_queryset=True):
|
|
52
52
|
"""
|
|
53
53
|
Return all ComputedFields assigned to the given model.
|
|
54
|
+
|
|
55
|
+
Returns a queryset by default, or a list if `get_queryset` param is False.
|
|
54
56
|
"""
|
|
55
57
|
concrete_model = model._meta.concrete_model
|
|
56
58
|
cache_key = f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
|
|
59
|
+
list_cache_key = f"{cache_key}.list"
|
|
60
|
+
if not get_queryset:
|
|
61
|
+
listing = cache.get(list_cache_key)
|
|
62
|
+
if listing is not None:
|
|
63
|
+
return listing
|
|
57
64
|
queryset = cache.get(cache_key)
|
|
58
65
|
if queryset is None:
|
|
59
66
|
content_type = ContentType.objects.get_for_model(concrete_model)
|
|
60
67
|
queryset = self.get_queryset().filter(content_type=content_type)
|
|
61
68
|
cache.set(cache_key, queryset)
|
|
69
|
+
if not get_queryset:
|
|
70
|
+
listing = list(queryset)
|
|
71
|
+
cache.set(list_cache_key, listing)
|
|
72
|
+
return listing
|
|
62
73
|
return queryset
|
|
63
74
|
|
|
64
75
|
get_for_model.cache_key_prefix = "nautobot.extras.computedfield.get_for_model"
|
|
65
76
|
|
|
77
|
+
def populate_list_caches(self):
|
|
78
|
+
"""Populate all caches for `get_for_model(..., get_queryset=False)` lookups."""
|
|
79
|
+
queryset = self.all().select_related("content_type")
|
|
80
|
+
listings = defaultdict(list)
|
|
81
|
+
for cf in queryset:
|
|
82
|
+
listings[f"{cf.content_type.app_label}.{cf.content_type.model}"].append(cf)
|
|
83
|
+
for ct in ContentType.objects.all():
|
|
84
|
+
label = f"{ct.app_label}.{ct.model}"
|
|
85
|
+
cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.list", listings[label])
|
|
86
|
+
|
|
66
87
|
|
|
67
88
|
@extras_features("graphql")
|
|
68
89
|
class ComputedField(
|
|
@@ -370,18 +391,24 @@ class CustomFieldModel(models.Model):
|
|
|
370
391
|
class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
371
392
|
use_in_migrations = True
|
|
372
393
|
|
|
373
|
-
def get_for_model(self, model, exclude_filter_disabled=False):
|
|
394
|
+
def get_for_model(self, model, exclude_filter_disabled=False, get_queryset=True):
|
|
374
395
|
"""
|
|
375
396
|
Return (and cache) all CustomFields assigned to the given model.
|
|
376
397
|
|
|
377
398
|
Args:
|
|
378
399
|
model (Model): The django model to which custom fields are registered
|
|
379
400
|
exclude_filter_disabled (bool): Exclude any custom fields which have filter logic disabled
|
|
401
|
+
get_queryset (bool): Whether to return a QuerySet or a list.
|
|
380
402
|
"""
|
|
381
403
|
concrete_model = model._meta.concrete_model
|
|
382
404
|
cache_key = (
|
|
383
405
|
f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}.{exclude_filter_disabled}"
|
|
384
406
|
)
|
|
407
|
+
list_cache_key = f"{cache_key}.list"
|
|
408
|
+
if not get_queryset:
|
|
409
|
+
listing = cache.get(list_cache_key)
|
|
410
|
+
if listing is not None:
|
|
411
|
+
return listing
|
|
385
412
|
queryset = cache.get(cache_key)
|
|
386
413
|
if queryset is None:
|
|
387
414
|
content_type = ContentType.objects.get_for_model(concrete_model)
|
|
@@ -389,6 +416,10 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
|
389
416
|
if exclude_filter_disabled:
|
|
390
417
|
queryset = queryset.exclude(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED)
|
|
391
418
|
cache.set(cache_key, queryset)
|
|
419
|
+
if not get_queryset:
|
|
420
|
+
listing = list(queryset)
|
|
421
|
+
cache.set(list_cache_key, listing)
|
|
422
|
+
return listing
|
|
392
423
|
return queryset
|
|
393
424
|
|
|
394
425
|
get_for_model.cache_key_prefix = "nautobot.extras.customfield.get_for_model"
|
|
@@ -405,6 +436,24 @@ class CustomFieldManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
|
405
436
|
|
|
406
437
|
keys_for_model.cache_key_prefix = "nautobot.extras.customfield.keys_for_model"
|
|
407
438
|
|
|
439
|
+
def populate_list_caches(self):
|
|
440
|
+
"""Populate all caches for `get_for_model(..., get_queryset=False)` and `keys_for_model` lookups."""
|
|
441
|
+
queryset = self.all().prefetch_related("content_types")
|
|
442
|
+
cf_listings = defaultdict(lambda: defaultdict(list))
|
|
443
|
+
key_listings = defaultdict(list)
|
|
444
|
+
for cf in queryset:
|
|
445
|
+
for ct in cf.content_types.all():
|
|
446
|
+
label = f"{ct.app_label}.{ct.model}"
|
|
447
|
+
cf_listings[label][False].append(cf)
|
|
448
|
+
if cf.filter_logic != CustomFieldFilterLogicChoices.FILTER_DISABLED:
|
|
449
|
+
cf_listings[label][True].append(cf)
|
|
450
|
+
key_listings[label].append(cf.key)
|
|
451
|
+
for ct in ContentType.objects.all():
|
|
452
|
+
label = f"{ct.app_label}.{ct.model}"
|
|
453
|
+
cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.True.list", cf_listings[label][True])
|
|
454
|
+
cache.set(f"{self.get_for_model.cache_key_prefix}.{label}.False.list", cf_listings[label][False])
|
|
455
|
+
cache.set(f"{self.keys_for_model.cache_key_prefix}.{label}", key_listings[label])
|
|
456
|
+
|
|
408
457
|
|
|
409
458
|
@extras_features("webhooks")
|
|
410
459
|
class CustomField(
|
|
@@ -465,8 +514,7 @@ class CustomField(
|
|
|
465
514
|
blank=True,
|
|
466
515
|
null=True,
|
|
467
516
|
help_text=(
|
|
468
|
-
|
|
469
|
-
'"Foo").'
|
|
517
|
+
'Default value for the field (must be a JSON value). Encapsulate strings with double quotes (e.g. "Foo").'
|
|
470
518
|
),
|
|
471
519
|
)
|
|
472
520
|
weight = models.PositiveSmallIntegerField(
|
|
@@ -100,8 +100,7 @@ class GitRepository(PrimaryModel):
|
|
|
100
100
|
|
|
101
101
|
if self.present_in_database and self.slug != self.__initial_slug:
|
|
102
102
|
raise ValidationError(
|
|
103
|
-
f"Slug cannot be changed once set. Current slug is {self.__initial_slug}, "
|
|
104
|
-
f"requested slug is {self.slug}"
|
|
103
|
+
f"Slug cannot be changed once set. Current slug is {self.__initial_slug}, requested slug is {self.slug}"
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
if not self.present_in_database:
|
nautobot/extras/models/jobs.py
CHANGED
|
@@ -543,6 +543,12 @@ class JobLogEntry(BaseModel):
|
|
|
543
543
|
ordering = ["created"]
|
|
544
544
|
get_latest_by = "created"
|
|
545
545
|
verbose_name_plural = "job log entries"
|
|
546
|
+
indexes = [
|
|
547
|
+
models.Index(
|
|
548
|
+
name="extras_joblog_jr_created_idx",
|
|
549
|
+
fields=["job_result", "created"],
|
|
550
|
+
)
|
|
551
|
+
]
|
|
546
552
|
|
|
547
553
|
|
|
548
554
|
#
|
|
@@ -644,6 +650,7 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
644
650
|
help_text="Registered name of the Celery task for this job. Internal use only.",
|
|
645
651
|
)
|
|
646
652
|
date_created = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
653
|
+
date_started = models.DateTimeField(null=True, blank=True, db_index=True)
|
|
647
654
|
date_done = models.DateTimeField(null=True, blank=True, db_index=True)
|
|
648
655
|
user = models.ForeignKey(
|
|
649
656
|
to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, related_name="+", blank=True, null=True
|
|
@@ -708,7 +715,7 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
708
715
|
natural_key_field_names = ["id"]
|
|
709
716
|
|
|
710
717
|
def __str__(self):
|
|
711
|
-
return f"{self.name}
|
|
718
|
+
return f"{self.name} created at {self.date_created} ({self.status})"
|
|
712
719
|
|
|
713
720
|
def as_dict(self):
|
|
714
721
|
"""This is required by the django-celery-results DB backend."""
|
|
@@ -730,7 +737,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
730
737
|
if not self.date_done:
|
|
731
738
|
return None
|
|
732
739
|
|
|
733
|
-
|
|
740
|
+
# Older records may not have a date_started value, so we use date_created as a fallback.
|
|
741
|
+
duration = self.date_done - (self.date_started or self.date_created)
|
|
734
742
|
minutes, seconds = divmod(duration.total_seconds(), 60)
|
|
735
743
|
|
|
736
744
|
return f"{int(minutes)} minutes, {seconds:.2f} seconds"
|
|
@@ -748,7 +756,8 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
748
756
|
# Only add metrics if we have a related job model. If we are moving to a terminal state we should always
|
|
749
757
|
# have a related job model, so this shouldn't be too tight of a restriction.
|
|
750
758
|
if self.job_model:
|
|
751
|
-
|
|
759
|
+
# Older records may not have a date_started value, so we use date_created as a fallback.
|
|
760
|
+
duration = self.date_done - (self.date_started or self.date_created)
|
|
752
761
|
JOB_RESULT_METRIC.labels(self.job_model.grouping, self.job_model.name, status).observe(
|
|
753
762
|
duration.total_seconds()
|
|
754
763
|
)
|
|
@@ -878,6 +887,7 @@ class JobResult(BaseModel, CustomFieldModel):
|
|
|
878
887
|
# synchronous tasks are run before the JobResult is saved, so any fields required by
|
|
879
888
|
# the job must be added before calling `apply()`
|
|
880
889
|
job_result.celery_kwargs = job_celery_kwargs
|
|
890
|
+
job_result.date_started = timezone.now()
|
|
881
891
|
job_result.save()
|
|
882
892
|
|
|
883
893
|
# setup synchronous task logging
|