nautobot 2.4.10__py3-none-any.whl → 2.4.11__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/cloud/tests/test_views.py +13 -1
- nautobot/cloud/views.py +39 -9
- nautobot/core/celery/__init__.py +21 -0
- nautobot/core/celery/encoders.py +3 -0
- nautobot/core/forms/forms.py +4 -1
- nautobot/core/jobs/bulk_actions.py +8 -8
- nautobot/core/jobs/cleanup.py +11 -0
- nautobot/core/management/commands/generate_test_data.py +2 -1
- nautobot/core/templates/generic/object_retrieve.html +1 -1
- nautobot/core/testing/mixins.py +19 -1
- nautobot/core/testing/views.py +104 -8
- nautobot/core/tests/test_jobs.py +20 -4
- nautobot/core/tests/test_utils.py +193 -0
- nautobot/core/tests/test_views_utils.py +53 -2
- nautobot/core/ui/object_detail.py +4 -0
- nautobot/core/utils/lookup.py +4 -2
- nautobot/core/utils/module_loading.py +86 -58
- nautobot/core/views/generic.py +2 -12
- nautobot/core/views/mixins.py +19 -1
- nautobot/core/views/renderers.py +4 -13
- nautobot/core/views/utils.py +16 -0
- nautobot/dcim/api/serializers.py +13 -0
- nautobot/dcim/api/urls.py +1 -0
- nautobot/dcim/api/views.py +20 -0
- nautobot/dcim/apps.py +1 -0
- nautobot/dcim/factory.py +11 -0
- nautobot/dcim/filters/__init__.py +110 -0
- nautobot/dcim/forms.py +205 -19
- nautobot/dcim/migrations/0070_modulefamily_models.py +92 -0
- nautobot/dcim/models/__init__.py +2 -0
- nautobot/dcim/models/device_component_templates.py +14 -0
- nautobot/dcim/models/device_components.py +13 -1
- nautobot/dcim/models/devices.py +62 -0
- nautobot/dcim/navigation.py +16 -0
- nautobot/dcim/tables/__init__.py +2 -0
- nautobot/dcim/tables/devices.py +48 -0
- nautobot/dcim/tables/devicetypes.py +35 -1
- nautobot/dcim/tables/template_code.py +2 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -90
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +1 -63
- nautobot/dcim/templates/dcim/location.html +2 -249
- nautobot/dcim/templates/dcim/location_edit.html +2 -38
- nautobot/dcim/templates/dcim/location_retrieve.html +249 -0
- nautobot/dcim/templates/dcim/location_update.html +38 -0
- nautobot/dcim/templates/dcim/module_update.html +1 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +93 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +31 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +6 -0
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -160
- nautobot/dcim/tests/test_api.py +35 -0
- nautobot/dcim/tests/test_filters.py +102 -3
- nautobot/dcim/tests/test_models.py +146 -0
- nautobot/dcim/tests/test_views.py +70 -97
- nautobot/dcim/urls.py +4 -22
- nautobot/dcim/views.py +439 -153
- nautobot/extras/api/views.py +9 -2
- nautobot/extras/datasources/git.py +11 -3
- nautobot/extras/forms/forms.py +9 -5
- nautobot/extras/jobs.py +4 -2
- nautobot/extras/models/datasources.py +5 -8
- nautobot/extras/models/jobs.py +5 -0
- nautobot/extras/plugins/__init__.py +3 -0
- nautobot/extras/tables.py +40 -3
- nautobot/extras/templates/extras/configcontext.html +2 -220
- nautobot/extras/templates/extras/configcontext_edit.html +2 -50
- nautobot/extras/templates/extras/configcontext_retrieve.html +2 -0
- nautobot/extras/templates/extras/configcontext_update.html +50 -0
- nautobot/extras/templates/extras/configcontextschema.html +2 -48
- nautobot/extras/templates/extras/configcontextschema_edit.html +2 -19
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +48 -0
- nautobot/extras/templates/extras/configcontextschema_update.html +19 -0
- nautobot/extras/templates/extras/inc/configcontext_data.html +1 -0
- nautobot/extras/templates/extras/inc/json_data.html +1 -1
- nautobot/extras/templates/extras/inc/json_format.html +2 -2
- nautobot/extras/templates/extras/job_edit.html +12 -6
- nautobot/extras/templates/extras/tag.html +2 -52
- nautobot/extras/templates/extras/tag_edit.html +2 -15
- nautobot/extras/templates/extras/tag_retrieve.html +52 -0
- nautobot/extras/templates/extras/tag_update.html +15 -0
- nautobot/extras/templates/extras/team_retrieve.html +2 -2
- nautobot/extras/tests/test_api.py +15 -15
- nautobot/extras/tests/test_filters.py +4 -4
- nautobot/extras/tests/test_jobs.py +23 -10
- nautobot/extras/tests/test_models.py +19 -8
- nautobot/extras/tests/test_plugins.py +6 -3
- nautobot/extras/tests/test_views.py +66 -11
- nautobot/extras/urls.py +4 -134
- nautobot/extras/views.py +113 -158
- nautobot/ipam/models.py +19 -4
- nautobot/ipam/tables.py +19 -0
- nautobot/ipam/templates/ipam/vlan.html +2 -84
- nautobot/ipam/templates/ipam/vlan_edit.html +2 -24
- nautobot/ipam/templates/ipam/vlan_retrieve.html +84 -0
- nautobot/ipam/templates/ipam/vlan_update.html +24 -0
- nautobot/ipam/tests/test_views.py +5 -0
- nautobot/ipam/urls.py +1 -21
- nautobot/ipam/views.py +45 -70
- nautobot/project-static/docs/404.html +31 -8
- nautobot/project-static/docs/apps/index.html +31 -8
- nautobot/project-static/docs/apps/nautobot-apps.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +120 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +31 -8
- nautobot/project-static/docs/development/apps/api/configuration-view.html +31 -8
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/global-search.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/graphql.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +40 -8
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +32 -9
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +31 -8
- nautobot/project-static/docs/development/apps/api/prometheus.html +31 -8
- nautobot/project-static/docs/development/apps/api/setup.html +31 -8
- nautobot/project-static/docs/development/apps/api/testing.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/base-template.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/notes.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/urls.html +31 -8
- nautobot/project-static/docs/development/apps/index.html +31 -8
- nautobot/project-static/docs/development/apps/migration/code-updates.html +31 -8
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +31 -8
- nautobot/project-static/docs/development/apps/migration/from-v1.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +31 -8
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +31 -8
- nautobot/project-static/docs/development/core/application-registry.html +31 -8
- nautobot/project-static/docs/development/core/best-practices.html +31 -8
- nautobot/project-static/docs/development/core/bootstrap-ui.html +31 -8
- nautobot/project-static/docs/development/core/caching.html +31 -8
- nautobot/project-static/docs/development/core/controllers.html +31 -8
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +31 -8
- nautobot/project-static/docs/development/core/generic-views.html +31 -8
- nautobot/project-static/docs/development/core/getting-started.html +31 -8
- nautobot/project-static/docs/development/core/homepage.html +31 -8
- nautobot/project-static/docs/development/core/index.html +31 -8
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +31 -8
- nautobot/project-static/docs/development/core/model-checklist.html +31 -8
- nautobot/project-static/docs/development/core/model-features.html +31 -8
- nautobot/project-static/docs/development/core/natural-keys.html +31 -8
- nautobot/project-static/docs/development/core/navigation-menu.html +31 -8
- nautobot/project-static/docs/development/core/release-checklist.html +31 -8
- nautobot/project-static/docs/development/core/role-internals.html +31 -8
- nautobot/project-static/docs/development/core/settings.html +31 -8
- nautobot/project-static/docs/development/core/style-guide.html +31 -8
- nautobot/project-static/docs/development/core/templates.html +31 -8
- nautobot/project-static/docs/development/core/testing.html +31 -8
- nautobot/project-static/docs/development/core/ui-component-framework.html +31 -8
- nautobot/project-static/docs/development/core/user-preferences.html +31 -8
- nautobot/project-static/docs/development/index.html +31 -8
- nautobot/project-static/docs/development/jobs/getting-started.html +35 -8
- nautobot/project-static/docs/development/jobs/index.html +31 -8
- nautobot/project-static/docs/development/jobs/installation.html +31 -8
- nautobot/project-static/docs/development/jobs/job-extensions.html +31 -8
- nautobot/project-static/docs/development/jobs/job-logging.html +31 -8
- nautobot/project-static/docs/development/jobs/job-patterns.html +31 -8
- nautobot/project-static/docs/development/jobs/job-structure.html +31 -8
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +31 -8
- nautobot/project-static/docs/development/jobs/testing.html +31 -8
- nautobot/project-static/docs/index.html +31 -8
- nautobot/project-static/docs/insert-analytics.sh +36 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +31 -8
- nautobot/project-static/docs/overview/design_philosophy.html +31 -8
- nautobot/project-static/docs/release-notes/index.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.0.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.1.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.2.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.3.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.4.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.5.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.6.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.0.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.1.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.2.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.3.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.4.html +252 -8
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +302 -298
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/services.html +31 -8
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +31 -8
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +31 -8
- nautobot/project-static/docs/user-guide/administration/security/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/security/notices.html +31 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +31 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +43 -20
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +10261 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +41 -15
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +31 -8
- nautobot/project-static/docs/user-guide/index.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +37 -9
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +31 -8
- nautobot/tenancy/tables.py +2 -0
- nautobot/virtualization/tests/test_views.py +1 -1
- nautobot/wireless/forms.py +0 -1
- nautobot/wireless/models.py +1 -1
- nautobot/wireless/tables.py +7 -0
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/METADATA +4 -4
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/RECORD +416 -401
- /nautobot/dcim/templates/dcim/{platform_edit.html → platform_create.html} +0 -0
- /nautobot/extras/test_jobs/{pass.py → pass_job.py} +0 -0
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/NOTICE +0 -0
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/WHEEL +0 -0
- {nautobot-2.4.10.dist-info → nautobot-2.4.11.dist-info}/entry_points.txt +0 -0
|
@@ -34,7 +34,6 @@ class CloudAccountTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
34
34
|
"provider": providers[1].pk,
|
|
35
35
|
"secrets_group": secrets_groups[1].pk,
|
|
36
36
|
"description": "New description",
|
|
37
|
-
"comments": "New comments",
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
def test_post_without_secrets_group(self):
|
|
@@ -56,6 +55,12 @@ class CloudAccountTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
56
55
|
|
|
57
56
|
class CloudNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
58
57
|
model = CloudNetwork
|
|
58
|
+
custom_action_required_permissions = {
|
|
59
|
+
"cloud:cloudnetwork_children": ["cloud.view_cloudnetwork"],
|
|
60
|
+
"cloud:cloudnetwork_prefixes": ["cloud.view_cloudnetwork", "ipam.view_prefix"],
|
|
61
|
+
"cloud:cloudnetwork_circuits": ["cloud.view_cloudnetwork", "circuits.view_circuit"],
|
|
62
|
+
"cloud:cloudnetwork_cloud_services": ["cloud.view_cloudnetwork", "cloud.view_cloudservice"],
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
@classmethod
|
|
61
66
|
def setUpTestData(cls):
|
|
@@ -104,6 +109,10 @@ class CloudNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
104
109
|
|
|
105
110
|
class CloudResourceTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
106
111
|
model = CloudResourceType
|
|
112
|
+
custom_action_required_permissions = {
|
|
113
|
+
"cloud:cloudresourcetype_services": ["cloud.view_cloudresourcetype", "cloud.view_cloudservice"],
|
|
114
|
+
"cloud:cloudresourcetype_networks": ["cloud.view_cloudresourcetype", "cloud.view_cloudnetwork"],
|
|
115
|
+
}
|
|
107
116
|
|
|
108
117
|
@classmethod
|
|
109
118
|
def setUpTestData(cls):
|
|
@@ -133,6 +142,9 @@ class CloudResourceTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
133
142
|
|
|
134
143
|
class CloudServiceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
135
144
|
model = CloudService
|
|
145
|
+
custom_action_required_permissions = {
|
|
146
|
+
"cloud:cloudservice_cloud_networks": ["cloud.view_cloudservice", "cloud.view_cloudnetwork"],
|
|
147
|
+
}
|
|
136
148
|
|
|
137
149
|
@classmethod
|
|
138
150
|
def setUpTestData(cls):
|
nautobot/cloud/views.py
CHANGED
|
@@ -64,7 +64,6 @@ class CloudNetworkUIViewSet(NautobotUIViewSet):
|
|
|
64
64
|
table_class = CloudNetworkTable
|
|
65
65
|
form_class = CloudNetworkForm
|
|
66
66
|
bulk_update_form_class = CloudNetworkBulkEditForm
|
|
67
|
-
|
|
68
67
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
69
68
|
panels=(
|
|
70
69
|
object_detail.ObjectFieldsPanel(
|
|
@@ -153,19 +152,35 @@ class CloudNetworkUIViewSet(NautobotUIViewSet):
|
|
|
153
152
|
),
|
|
154
153
|
)
|
|
155
154
|
|
|
156
|
-
@action(detail=True, url_path="children")
|
|
155
|
+
@action(detail=True, url_path="children", custom_view_base_action="view")
|
|
157
156
|
def children(self, request, *args, **kwargs):
|
|
158
157
|
return Response({})
|
|
159
158
|
|
|
160
|
-
@action(
|
|
159
|
+
@action(
|
|
160
|
+
detail=True,
|
|
161
|
+
url_path="prefixes",
|
|
162
|
+
custom_view_base_action="view",
|
|
163
|
+
custom_view_additional_permissions=["ipam.view_prefix"],
|
|
164
|
+
)
|
|
161
165
|
def prefixes(self, request, *args, **kwargs):
|
|
162
166
|
return Response({})
|
|
163
167
|
|
|
164
|
-
@action(
|
|
168
|
+
@action(
|
|
169
|
+
detail=True,
|
|
170
|
+
url_path="circuits",
|
|
171
|
+
custom_view_base_action="view",
|
|
172
|
+
custom_view_additional_permissions=["circuits.view_circuit"],
|
|
173
|
+
)
|
|
165
174
|
def circuits(self, request, *args, **kwargs):
|
|
166
175
|
return Response({})
|
|
167
176
|
|
|
168
|
-
@action(
|
|
177
|
+
@action(
|
|
178
|
+
detail=True,
|
|
179
|
+
url_path="cloud-services",
|
|
180
|
+
url_name="cloud_services",
|
|
181
|
+
custom_view_base_action="view",
|
|
182
|
+
custom_view_additional_permissions=["cloud.view_cloudservice"],
|
|
183
|
+
)
|
|
169
184
|
def cloud_services(self, request, *args, **kwargs):
|
|
170
185
|
return Response({})
|
|
171
186
|
|
|
@@ -178,7 +193,6 @@ class CloudResourceTypeUIViewSet(NautobotUIViewSet):
|
|
|
178
193
|
table_class = CloudResourceTypeTable
|
|
179
194
|
form_class = CloudResourceTypeForm
|
|
180
195
|
bulk_update_form_class = CloudResourceTypeBulkEditForm
|
|
181
|
-
|
|
182
196
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
183
197
|
panels=(
|
|
184
198
|
object_detail.ObjectFieldsPanel(
|
|
@@ -230,11 +244,21 @@ class CloudResourceTypeUIViewSet(NautobotUIViewSet):
|
|
|
230
244
|
),
|
|
231
245
|
)
|
|
232
246
|
|
|
233
|
-
@action(
|
|
247
|
+
@action(
|
|
248
|
+
detail=True,
|
|
249
|
+
url_path="networks",
|
|
250
|
+
custom_view_base_action="view",
|
|
251
|
+
custom_view_additional_permissions=["cloud.view_cloudnetwork"],
|
|
252
|
+
)
|
|
234
253
|
def networks(self, request, *args, **kwargs):
|
|
235
254
|
return Response({})
|
|
236
255
|
|
|
237
|
-
@action(
|
|
256
|
+
@action(
|
|
257
|
+
detail=True,
|
|
258
|
+
url_path="services",
|
|
259
|
+
custom_view_base_action="view",
|
|
260
|
+
custom_view_additional_permissions=["cloud.view_cloudservice"],
|
|
261
|
+
)
|
|
238
262
|
def services(self, request, *args, **kwargs):
|
|
239
263
|
return Response({})
|
|
240
264
|
|
|
@@ -284,6 +308,12 @@ class CloudServiceUIViewSet(NautobotUIViewSet):
|
|
|
284
308
|
),
|
|
285
309
|
)
|
|
286
310
|
|
|
287
|
-
@action(
|
|
311
|
+
@action(
|
|
312
|
+
detail=True,
|
|
313
|
+
url_path="cloud-networks",
|
|
314
|
+
url_name="cloud_networks",
|
|
315
|
+
custom_view_base_action="view",
|
|
316
|
+
custom_view_additional_permissions=["cloud.view_cloudnetwork"],
|
|
317
|
+
)
|
|
288
318
|
def cloud_networks(self, request, *args, **kwargs):
|
|
289
319
|
return Response({})
|
nautobot/core/celery/__init__.py
CHANGED
|
@@ -3,10 +3,12 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import shutil
|
|
6
|
+
import sys
|
|
6
7
|
|
|
7
8
|
from celery import Celery, shared_task, signals
|
|
8
9
|
from celery.app.log import TaskFormatter
|
|
9
10
|
from celery.utils.log import get_logger
|
|
11
|
+
from django.apps import apps
|
|
10
12
|
from django.conf import settings
|
|
11
13
|
from django.db.utils import OperationalError, ProgrammingError
|
|
12
14
|
from django.utils.functional import SimpleLazyObject
|
|
@@ -19,6 +21,7 @@ from nautobot.core.celery.control import discard_git_repository, refresh_git_rep
|
|
|
19
21
|
from nautobot.core.celery.encoders import NautobotKombuJSONEncoder
|
|
20
22
|
from nautobot.core.celery.log import NautobotDatabaseHandler
|
|
21
23
|
from nautobot.core.utils.module_loading import import_modules_privately
|
|
24
|
+
from nautobot.extras.plugins.utils import import_object
|
|
22
25
|
from nautobot.extras.registry import registry
|
|
23
26
|
|
|
24
27
|
logger = logging.getLogger(__name__)
|
|
@@ -47,12 +50,14 @@ app.autodiscover_tasks()
|
|
|
47
50
|
def import_jobs(sender=None, **kwargs):
|
|
48
51
|
"""
|
|
49
52
|
Import system Jobs into Nautobot as well as Jobs from JOBS_ROOT and GIT_ROOT.
|
|
53
|
+
Import app-provided jobs if the app provides dynamic jobs.
|
|
50
54
|
|
|
51
55
|
Note that app-provided jobs are automatically imported at startup time via NautobotAppConfig.ready()
|
|
52
56
|
"""
|
|
53
57
|
import nautobot.core.jobs # noqa: F401
|
|
54
58
|
|
|
55
59
|
_import_jobs_from_jobs_root()
|
|
60
|
+
_import_dynamic_jobs_from_apps()
|
|
56
61
|
|
|
57
62
|
try:
|
|
58
63
|
_import_jobs_from_git_repositories()
|
|
@@ -120,6 +125,22 @@ def _import_jobs_from_git_repositories():
|
|
|
120
125
|
refresh_git_repository(state=None, repository_pk=repo.pk, head=repo.current_head)
|
|
121
126
|
|
|
122
127
|
|
|
128
|
+
def _import_dynamic_jobs_from_apps():
|
|
129
|
+
for app_name in settings.PLUGINS:
|
|
130
|
+
app_config = apps.get_app_config(app_name)
|
|
131
|
+
if not getattr(app_config, "provides_dynamic_jobs", False):
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
# Unload job modules from sys.modules if they were previously loaded
|
|
135
|
+
app_jobs = getattr(app_config, "features", {}).get("jobs", [])
|
|
136
|
+
for job in app_jobs:
|
|
137
|
+
if job.__module__ in sys.modules:
|
|
138
|
+
del sys.modules[job.__module__]
|
|
139
|
+
|
|
140
|
+
# Load app jobs
|
|
141
|
+
app_config.features["jobs"] = import_object(f"{app_config.__module__}.{app_config.jobs}")
|
|
142
|
+
|
|
143
|
+
|
|
123
144
|
def add_nautobot_log_handler(logger_instance, log_format=None):
|
|
124
145
|
"""Add NautobotDatabaseHandler to logger and update logger level filtering to send all log levels to our handler."""
|
|
125
146
|
if any(isinstance(h, NautobotDatabaseHandler) for h in logger_instance.handlers):
|
nautobot/core/celery/encoders.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from zoneinfo import ZoneInfo
|
|
2
3
|
|
|
3
4
|
from django.db import models
|
|
4
5
|
from rest_framework.utils.encoders import JSONEncoder
|
|
@@ -52,5 +53,7 @@ class NautobotKombuJSONEncoder(JSONEncoder):
|
|
|
52
53
|
# JobResult.result uses NautobotKombuJSONEncoder as an encoder and expects a JSONSerializable object,
|
|
53
54
|
# although an exception, such as a RuntimeException, can be supplied as the obj.
|
|
54
55
|
return f"{obj.__class__.__name__}: {obj}"
|
|
56
|
+
elif isinstance(obj, ZoneInfo):
|
|
57
|
+
return obj.key
|
|
55
58
|
else:
|
|
56
59
|
return super().default(obj)
|
nautobot/core/forms/forms.py
CHANGED
|
@@ -138,7 +138,10 @@ class BulkEditForm(forms.Form):
|
|
|
138
138
|
# Handle M2M Save
|
|
139
139
|
for key in self.cleaned_data.keys():
|
|
140
140
|
if key.startswith(("add_", "remove_")):
|
|
141
|
-
|
|
141
|
+
if key.startswith("add_"):
|
|
142
|
+
field_name = key.lstrip("add_")
|
|
143
|
+
else:
|
|
144
|
+
field_name = key.lstrip("remove_")
|
|
142
145
|
if field_name in m2m_field_names:
|
|
143
146
|
continue
|
|
144
147
|
with contextlib.suppress(FieldDoesNotExist):
|
|
@@ -96,12 +96,11 @@ class BulkEditObjects(Job):
|
|
|
96
96
|
model_field = None
|
|
97
97
|
|
|
98
98
|
# Handle nullification
|
|
99
|
-
if nullified_fields:
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
else
|
|
104
|
-
setattr(obj, field_name, None if model_field is not None and model_field.null else "")
|
|
99
|
+
if nullified_fields and field_name in nullified_fields and field_name in form.nullable_fields:
|
|
100
|
+
if isinstance(model_field, ManyToManyField):
|
|
101
|
+
getattr(obj, field_name).set([])
|
|
102
|
+
else:
|
|
103
|
+
setattr(obj, field_name, None if model_field is not None and model_field.null else "")
|
|
105
104
|
|
|
106
105
|
# ManyToManyFields
|
|
107
106
|
elif isinstance(model_field, ManyToManyField):
|
|
@@ -109,7 +108,8 @@ class BulkEditObjects(Job):
|
|
|
109
108
|
getattr(obj, field_name).set(form.cleaned_data[field_name])
|
|
110
109
|
# Normal fields
|
|
111
110
|
elif form.cleaned_data[field_name] not in (None, "", []):
|
|
112
|
-
|
|
111
|
+
if hasattr(obj, field_name):
|
|
112
|
+
setattr(obj, field_name, form.cleaned_data[field_name])
|
|
113
113
|
|
|
114
114
|
# Update custom fields
|
|
115
115
|
for field_name in form_custom_fields:
|
|
@@ -121,7 +121,7 @@ class BulkEditObjects(Job):
|
|
|
121
121
|
obj.full_clean()
|
|
122
122
|
obj.save()
|
|
123
123
|
updated_objects_pk.append(obj.pk)
|
|
124
|
-
form.post_save(obj)
|
|
124
|
+
form.post_save(obj) # handles M2M add_* and remove_* form fields
|
|
125
125
|
|
|
126
126
|
if hasattr(form, "save_relationships") and callable(form.save_relationships):
|
|
127
127
|
# Add/remove relationship associations
|
nautobot/core/jobs/cleanup.py
CHANGED
|
@@ -67,6 +67,17 @@ class LogsCleanup(Job):
|
|
|
67
67
|
cascade_queryset = related_model.objects.filter(**{f"{related_field_name}__id__in": queryset})
|
|
68
68
|
self.recursive_delete_with_cascade(cascade_queryset, deletion_summary)
|
|
69
69
|
|
|
70
|
+
genericrelation_related_fields = [
|
|
71
|
+
field for field in queryset.model._meta.private_fields if hasattr(field, "bulk_related_objects")
|
|
72
|
+
]
|
|
73
|
+
for gr_related_field in genericrelation_related_fields:
|
|
74
|
+
related_model = gr_related_field.related_model
|
|
75
|
+
if related_model == queryset.model:
|
|
76
|
+
continue # avoid infinite recursion # TODO: this won't catch A<-B<-A<-B loops...
|
|
77
|
+
related_field_name = gr_related_field.related_query_name()
|
|
78
|
+
cascade_queryset = related_model.objects.filter(**{f"{related_field_name}__id__in": queryset})
|
|
79
|
+
self.recursive_delete_with_cascade(cascade_queryset, deletion_summary)
|
|
80
|
+
|
|
70
81
|
deleted_count = queryset._raw_delete(using="default")
|
|
71
82
|
if deleted_count:
|
|
72
83
|
deletion_summary.update({queryset.model._meta.label: deleted_count})
|
|
@@ -225,7 +225,8 @@ class Command(BaseCommand):
|
|
|
225
225
|
_create_batch(InterfaceTemplateFactory, 30)
|
|
226
226
|
_create_batch(PowerPortTemplateFactory, 30)
|
|
227
227
|
_create_batch(PowerOutletTemplateFactory, 30)
|
|
228
|
-
_create_batch(ModuleBayTemplateFactory,
|
|
228
|
+
_create_batch(ModuleBayTemplateFactory, 60, description="without module families", has_module_family=False)
|
|
229
|
+
_create_batch(ModuleBayTemplateFactory, 30, description="with module families", has_module_family=True)
|
|
229
230
|
_create_batch(ManufacturerFactory, 2, description="without Platforms or DeviceTypes") # Last 2 hard-coded
|
|
230
231
|
_create_batch(DeviceRedundancyGroupFactory, 20)
|
|
231
232
|
_create_batch(DeviceFactory, 20)
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
<div class="col-md-6">
|
|
145
145
|
{% block content_left_page %}{% endblock content_left_page %}
|
|
146
146
|
{% include 'inc/custom_fields/panel.html' with custom_fields=object.get_custom_field_groupings_basic custom_fields_advanced_ui=False computed_fields=object.get_computed_fields_grouping_basic computed_fields_advanced_ui=False %}
|
|
147
|
-
{% include 'inc/
|
|
147
|
+
{% include 'inc/relationships/panel_override.html' with relationships_fields_override=object.get_relationships_data_basic_fields %}
|
|
148
148
|
{% include 'extras/inc/tags_panel.html' %}
|
|
149
149
|
{% plugin_left_page object %}
|
|
150
150
|
</div>
|
nautobot/core/testing/mixins.py
CHANGED
|
@@ -5,7 +5,7 @@ from django.apps import apps
|
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
7
|
from django.core.cache import cache
|
|
8
|
-
from django.core.exceptions import FieldDoesNotExist
|
|
8
|
+
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
|
9
9
|
from django.db import connections, DEFAULT_DB_ALIAS
|
|
10
10
|
from django.db.models import JSONField, ManyToManyField, ManyToManyRel
|
|
11
11
|
from django.forms.models import model_to_dict
|
|
@@ -161,6 +161,24 @@ class NautobotTestCaseMixin:
|
|
|
161
161
|
obj_perm.users.add(self.user)
|
|
162
162
|
obj_perm.object_types.add(ct)
|
|
163
163
|
|
|
164
|
+
def remove_permissions(self, *names, **kwargs):
|
|
165
|
+
"""
|
|
166
|
+
Remove a set of permissions. Accepts permission names in the form <app>.<action>_<model>.
|
|
167
|
+
Additional keyword arguments will be passed to the ObjectPermission constructor to allow creating more detailed permissions.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
>>> remove_permissions("ipam.add_vlangroup", "ipam.view_vlangroup")
|
|
171
|
+
>>> remove_permissions("ipam.add_vlangroup", "ipam.view_vlangroup", constraints={"pk": "uuid-1234"})
|
|
172
|
+
"""
|
|
173
|
+
for name in names:
|
|
174
|
+
_, action, _ = permissions.resolve_permission(name)
|
|
175
|
+
try:
|
|
176
|
+
obj_perm = users_models.ObjectPermission.objects.get(name=name, actions=[action], **kwargs)
|
|
177
|
+
obj_perm.delete()
|
|
178
|
+
except ObjectDoesNotExist:
|
|
179
|
+
# Permission does not exist, so nothing to remove
|
|
180
|
+
pass
|
|
181
|
+
|
|
164
182
|
#
|
|
165
183
|
# Custom assertions
|
|
166
184
|
#
|
nautobot/core/testing/views.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import re
|
|
3
3
|
from typing import Optional, Sequence
|
|
4
|
-
from unittest import skipIf
|
|
4
|
+
from unittest import mock, skipIf
|
|
5
5
|
import uuid
|
|
6
6
|
|
|
7
7
|
from django.apps import apps
|
|
@@ -9,6 +9,7 @@ from django.conf import settings
|
|
|
9
9
|
from django.contrib.contenttypes.models import ContentType
|
|
10
10
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
11
11
|
from django.core.validators import URLValidator
|
|
12
|
+
from django.db.models import ManyToManyField, Model, QuerySet
|
|
12
13
|
from django.test import override_settings, tag, TestCase as _TestCase
|
|
13
14
|
from django.urls import NoReverseMatch, reverse
|
|
14
15
|
from django.utils.html import escape
|
|
@@ -16,6 +17,7 @@ from django.utils.http import urlencode
|
|
|
16
17
|
from django.utils.text import slugify
|
|
17
18
|
from tree_queries.models import TreeNode
|
|
18
19
|
|
|
20
|
+
from nautobot.core.jobs.bulk_actions import BulkEditObjects
|
|
19
21
|
from nautobot.core.models.generics import PrimaryModel
|
|
20
22
|
from nautobot.core.models.tree_queries import TreeModel
|
|
21
23
|
from nautobot.core.templatetags import helpers
|
|
@@ -141,6 +143,8 @@ class ViewTestCases:
|
|
|
141
143
|
Retrieve a single instance.
|
|
142
144
|
"""
|
|
143
145
|
|
|
146
|
+
custom_action_required_permissions = {}
|
|
147
|
+
|
|
144
148
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
145
149
|
def test_get_object_anonymous(self):
|
|
146
150
|
# Make the request as an unauthenticated user
|
|
@@ -246,6 +250,21 @@ class ViewTestCases:
|
|
|
246
250
|
self.assertBodyContains(response, f"{instance.get_absolute_url()}#advanced")
|
|
247
251
|
self.assertBodyContains(response, "Advanced")
|
|
248
252
|
|
|
253
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
254
|
+
def test_custom_actions(self):
|
|
255
|
+
instance = self._get_queryset().first()
|
|
256
|
+
for url_name, required_permissions in self.custom_action_required_permissions.items():
|
|
257
|
+
url = reverse(url_name, kwargs={"pk": instance.pk})
|
|
258
|
+
self.assertHttpStatus(self.client.get(url), 403)
|
|
259
|
+
for permission in required_permissions[:-1]:
|
|
260
|
+
self.add_permissions(permission)
|
|
261
|
+
self.assertHttpStatus(self.client.get(url), 403)
|
|
262
|
+
|
|
263
|
+
self.add_permissions(required_permissions[-1])
|
|
264
|
+
self.assertHttpStatus(self.client.get(url), 200)
|
|
265
|
+
# delete the permissions here so that repetitive calls to add_permissions do not create duplicate permissions.
|
|
266
|
+
self.remove_permissions(*required_permissions)
|
|
267
|
+
|
|
249
268
|
class GetObjectChangelogViewTestCase(ModelViewTestCase):
|
|
250
269
|
"""
|
|
251
270
|
View the changelog for an instance.
|
|
@@ -769,6 +788,17 @@ class ViewTestCases:
|
|
|
769
788
|
response_body = response.content.decode(response.charset)
|
|
770
789
|
self.assertIn("/login/?next=" + self._get_url("list"), response_body, msg=response_body)
|
|
771
790
|
|
|
791
|
+
def test_list_objects_anonymous_with_exempt_permission_for_one_view_only(self):
|
|
792
|
+
# Make the request as an unauthenticated user
|
|
793
|
+
self.client.logout()
|
|
794
|
+
# Test if AnonymousUser can properly render the whole list view
|
|
795
|
+
with override_settings(EXEMPT_VIEW_PERMISSIONS=[self.model._meta.label_lower]):
|
|
796
|
+
response = self.client.get(self._get_url("list"))
|
|
797
|
+
self.assertHttpStatus(response, 200)
|
|
798
|
+
# There should be some rows
|
|
799
|
+
self.assertBodyContains(response, '<tr class="even')
|
|
800
|
+
self.assertBodyContains(response, '<tr class="odd')
|
|
801
|
+
|
|
772
802
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
773
803
|
def test_list_objects_filtered(self):
|
|
774
804
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
@@ -1049,6 +1079,7 @@ class ViewTestCases:
|
|
|
1049
1079
|
def validate_redirect_to_job_result(self, response):
|
|
1050
1080
|
# Get the last Bulk Edit Objects JobResult created
|
|
1051
1081
|
job_result = JobResult.objects.filter(name="Bulk Edit Objects").first()
|
|
1082
|
+
self.assertIsNotNone(job_result, "No JobResult was created - likely the bulk_edit_data is invalid!")
|
|
1052
1083
|
# Assert redirect to Job Results
|
|
1053
1084
|
self.assertRedirects(
|
|
1054
1085
|
response,
|
|
@@ -1082,8 +1113,71 @@ class ViewTestCases:
|
|
|
1082
1113
|
# Assign model-level permission
|
|
1083
1114
|
self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
|
|
1084
1115
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1116
|
+
with mock.patch.object(JobResult, "enqueue_job", wraps=JobResult.enqueue_job) as mock_enqueue_job:
|
|
1117
|
+
response = self.client.post(self._get_url("bulk_edit"), data)
|
|
1118
|
+
self.validate_redirect_to_job_result(response)
|
|
1119
|
+
mock_enqueue_job.assert_called()
|
|
1120
|
+
|
|
1121
|
+
# Verify that the provided self.bulk_edit_data was passed through correctly to the job.
|
|
1122
|
+
# The below is a bit gross because of multiple layers of data encoding and decoding involved. Sorry!
|
|
1123
|
+
job_form = BulkEditObjects.as_form(BulkEditObjects.deserialize_data(mock_enqueue_job.call_args.kwargs))
|
|
1124
|
+
job_form.is_valid()
|
|
1125
|
+
job_kwargs = job_form.cleaned_data
|
|
1126
|
+
|
|
1127
|
+
bulk_edit_form_class = lookup.get_form_for_model(self.model, form_prefix="BulkEdit")
|
|
1128
|
+
bulk_edit_form = bulk_edit_form_class(self.model, job_kwargs["form_data"])
|
|
1129
|
+
bulk_edit_form.is_valid()
|
|
1130
|
+
passed_bulk_edit_data = bulk_edit_form.cleaned_data
|
|
1131
|
+
|
|
1132
|
+
for key, value in self.bulk_edit_data.items():
|
|
1133
|
+
with self.subTest(key=key):
|
|
1134
|
+
if isinstance(passed_bulk_edit_data.get(key), Model):
|
|
1135
|
+
self.assertEqual(passed_bulk_edit_data.get(key).pk, value)
|
|
1136
|
+
elif isinstance(passed_bulk_edit_data.get(key), QuerySet):
|
|
1137
|
+
self.assertEqual(
|
|
1138
|
+
sorted(passed_bulk_edit_data.get(key).values_list("pk", flat=True)), sorted(value)
|
|
1139
|
+
)
|
|
1140
|
+
else:
|
|
1141
|
+
self.assertEqual(passed_bulk_edit_data.get(key), bulk_edit_form.fields[key].clean(value))
|
|
1142
|
+
|
|
1143
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1144
|
+
def test_bulk_edit_objects_nullable_fields(self):
|
|
1145
|
+
"""Assert that "set null" fields on the bulk-edit form are correctly passed through to the job."""
|
|
1146
|
+
bulk_edit_form_class = lookup.get_form_for_model(self.model, form_prefix="BulkEdit")
|
|
1147
|
+
bulk_edit_form = bulk_edit_form_class(self.model)
|
|
1148
|
+
if not getattr(bulk_edit_form, "nullable_fields", ()):
|
|
1149
|
+
self.skipTest(f"no nullable fields on {bulk_edit_form_class}")
|
|
1150
|
+
|
|
1151
|
+
for field_name in bulk_edit_form.nullable_fields:
|
|
1152
|
+
with self.subTest(field_name=field_name):
|
|
1153
|
+
if field_name.startswith("cf_"):
|
|
1154
|
+
# TODO check whether customfield is nullable
|
|
1155
|
+
continue
|
|
1156
|
+
if field_name.startswith("cr_"):
|
|
1157
|
+
# TODO check whether relationship is required
|
|
1158
|
+
continue
|
|
1159
|
+
model_field = self.model._meta.get_field(field_name)
|
|
1160
|
+
if isinstance(model_field, ManyToManyField):
|
|
1161
|
+
# always nullable
|
|
1162
|
+
continue
|
|
1163
|
+
self.assertTrue(model_field.null or model_field.blank)
|
|
1164
|
+
|
|
1165
|
+
pk_list = list(self._get_queryset().values_list("pk", flat=True)[:3])
|
|
1166
|
+
data = {
|
|
1167
|
+
"pk": pk_list,
|
|
1168
|
+
"_apply": True, # Form button
|
|
1169
|
+
"_nullify": list(bulk_edit_form.nullable_fields),
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
# Assign model-level permission
|
|
1173
|
+
self.add_permissions(f"{self.model._meta.app_label}.change_{self.model._meta.model_name}")
|
|
1174
|
+
|
|
1175
|
+
with mock.patch.object(JobResult, "enqueue_job", wraps=JobResult.enqueue_job) as mock_enqueue_job:
|
|
1176
|
+
response = self.client.post(self._get_url("bulk_edit"), data)
|
|
1177
|
+
self.validate_redirect_to_job_result(response)
|
|
1178
|
+
mock_enqueue_job.assert_called()
|
|
1179
|
+
|
|
1180
|
+
self.assertEqual(mock_enqueue_job.call_args.kwargs["form_data"].get("_nullify"), data["_nullify"])
|
|
1087
1181
|
|
|
1088
1182
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1089
1183
|
def test_bulk_edit_form_contains_all_pks(self):
|
|
@@ -1182,11 +1276,13 @@ class ViewTestCases:
|
|
|
1182
1276
|
}
|
|
1183
1277
|
data.update(utils.post_data(self.bulk_edit_data))
|
|
1184
1278
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1279
|
+
with mock.patch.object(JobResult, "enqueue_job", wraps=JobResult.enqueue_job) as mock_enqueue_job:
|
|
1280
|
+
# Attempt to bulk edit permitted objects into a non-permitted state
|
|
1281
|
+
response = self.client.post(self._get_url("bulk_edit"), data)
|
|
1282
|
+
# NOTE: There is no way of testing constrained failure as bulk edit is a system Job;
|
|
1283
|
+
# and can only be tested by checking JobLogs.
|
|
1284
|
+
self.validate_redirect_to_job_result(response)
|
|
1285
|
+
mock_enqueue_job.assert_called()
|
|
1190
1286
|
|
|
1191
1287
|
class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
|
|
1192
1288
|
"""
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -689,10 +689,10 @@ class LogsCleanupTestCase(TransactionTestCase):
|
|
|
689
689
|
cleanup_types=[CleanupTypes.JOB_RESULT],
|
|
690
690
|
max_age=60,
|
|
691
691
|
)
|
|
692
|
-
self.assertFalse(JobResult.objects.filter(date_done__lt=cutoff).exists())
|
|
693
|
-
self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists())
|
|
694
|
-
self.assertTrue(ObjectChange.objects.filter(time__lt=cutoff).exists())
|
|
695
|
-
self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists())
|
|
692
|
+
self.assertFalse(JobResult.objects.filter(date_done__lt=cutoff).exists(), cm.output)
|
|
693
|
+
self.assertTrue(JobResult.objects.filter(date_done__gte=cutoff).exists(), cm.output)
|
|
694
|
+
self.assertTrue(ObjectChange.objects.filter(time__lt=cutoff).exists(), cm.output)
|
|
695
|
+
self.assertTrue(ObjectChange.objects.filter(time__gte=cutoff).exists(), cm.output)
|
|
696
696
|
|
|
697
697
|
started_logs = {
|
|
698
698
|
"job_result_id": str(job_result.id),
|
|
@@ -840,6 +840,22 @@ class BulkEditTestCase(TransactionTestCase):
|
|
|
840
840
|
)
|
|
841
841
|
self._common_no_error_test_assertion(Role, job_result, Role.objects.all().count(), color="aa1409")
|
|
842
842
|
|
|
843
|
+
def test_bulk_edit_objects_nullify(self):
|
|
844
|
+
"""
|
|
845
|
+
Bulk edit Role instances to nullify their weight.
|
|
846
|
+
"""
|
|
847
|
+
self.add_permissions("extras.change_role", "extras.view_role")
|
|
848
|
+
job_result = create_job_result_and_run_job(
|
|
849
|
+
"nautobot.core.jobs.bulk_actions",
|
|
850
|
+
"BulkEditObjects",
|
|
851
|
+
content_type=self.role_ct.id,
|
|
852
|
+
edit_all=True,
|
|
853
|
+
filter_query_params={},
|
|
854
|
+
form_data={"_nullify": ["weight"]},
|
|
855
|
+
username=self.user.username,
|
|
856
|
+
)
|
|
857
|
+
self._common_no_error_test_assertion(Role, job_result, Role.objects.all().count(), weight__isnull=True)
|
|
858
|
+
|
|
843
859
|
def test_bulk_edit_select_some(self):
|
|
844
860
|
"""
|
|
845
861
|
Bulk edit selected Namespace instances.
|