nautobot 2.2.4__py3-none-any.whl → 2.2.6__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/api.py +2 -0
- nautobot/apps/models.py +2 -0
- nautobot/circuits/forms.py +15 -0
- nautobot/circuits/navigation.py +9 -1
- nautobot/circuits/views.py +2 -0
- nautobot/core/api/fields.py +13 -0
- nautobot/core/api/serializers.py +7 -1
- nautobot/core/filters.py +11 -0
- nautobot/core/management/commands/generate_test_data.py +128 -158
- nautobot/core/models/fields.py +15 -0
- nautobot/core/settings.yaml +3 -3
- nautobot/core/testing/filters.py +24 -1
- nautobot/core/testing/views.py +13 -1
- nautobot/core/tests/test_utils.py +48 -1
- nautobot/core/utils/git.py +121 -49
- nautobot/core/utils/module_loading.py +10 -2
- nautobot/core/views/utils.py +18 -1
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filters/__init__.py +1 -1
- nautobot/dcim/forms.py +23 -4
- nautobot/dcim/tables/devicetypes.py +15 -4
- nautobot/dcim/tests/test_models.py +2 -0
- nautobot/dcim/tests/test_views.py +84 -0
- nautobot/dcim/views.py +3 -0
- nautobot/extras/api/views.py +2 -2
- nautobot/extras/context_managers.py +3 -0
- nautobot/extras/datasources/git.py +133 -135
- nautobot/extras/datasources/utils.py +3 -0
- nautobot/extras/filters/__init__.py +16 -1
- nautobot/extras/forms/forms.py +49 -3
- nautobot/extras/forms/mixins.py +0 -6
- nautobot/extras/jobs.py +9 -1
- nautobot/extras/migrations/0107_laxurlfield.py +28 -0
- nautobot/extras/migrations/0108_jobbutton_enabled.py +17 -0
- nautobot/extras/models/datasources.py +6 -4
- nautobot/extras/models/jobs.py +30 -0
- nautobot/extras/models/models.py +2 -4
- nautobot/extras/signals.py +6 -1
- nautobot/extras/tables.py +3 -0
- nautobot/extras/templates/extras/jobbutton_retrieve.html +6 -2
- nautobot/extras/templatetags/job_buttons.py +2 -2
- nautobot/extras/tests/git_data/01-valid-files/__init__.py +0 -0
- nautobot/extras/tests/git_data/01-valid-files/config_context_schemas/schema-1.yaml +18 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/context.yaml +12 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/devices/test-device.json +3 -0
- nautobot/extras/tests/git_data/01-valid-files/config_contexts/locations/Test Location.json +7 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template.j2 +3 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template2.html +4 -0
- nautobot/extras/tests/git_data/01-valid-files/export_templates/ipam/vlan/template.j2 +3 -0
- nautobot/extras/tests/git_data/01-valid-files/jobs/__init__.py +5 -0
- nautobot/extras/tests/git_data/01-valid-files/jobs/my_job.py +16 -0
- nautobot/extras/tests/git_data/02-invalid-files/__init__.py +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema1.json +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema2.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext1.json +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext2.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext3.json +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/config_contexts/devices/nosuchdevice.json +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/dcim/template.j2 +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/devices/template.j2 +0 -0
- nautobot/extras/tests/git_data/02-invalid-files/export_templates/dcim/nosuchmodel/template.j2 +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/export_templates/nosuchapp/device/template.j2 +3 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/__init__.py +2 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/importerror.py +1 -0
- nautobot/extras/tests/git_data/02-invalid-files/jobs/syntaxerror.py +1 -0
- nautobot/extras/tests/git_helper.py +76 -0
- nautobot/extras/tests/test_api.py +52 -13
- nautobot/extras/tests/test_context_managers.py +33 -1
- nautobot/extras/tests/test_datasources.py +94 -276
- nautobot/extras/tests/test_filters.py +69 -0
- nautobot/extras/tests/test_forms.py +0 -3
- nautobot/extras/tests/test_models.py +8 -3
- nautobot/extras/tests/test_views.py +69 -11
- nautobot/extras/views.py +12 -10
- nautobot/ipam/filters.py +9 -1
- nautobot/ipam/forms.py +26 -0
- nautobot/ipam/tables.py +1 -1
- nautobot/ipam/tests/test_filters.py +15 -0
- nautobot/ipam/tests/test_views.py +9 -2
- nautobot/ipam/views.py +11 -0
- nautobot/project-static/docs/404.html +84 -9
- nautobot/project-static/docs/apps/index.html +97 -11
- nautobot/project-static/docs/apps/nautobot-apps.html +97 -11
- nautobot/project-static/docs/assets/app-icons/icon-CapacityMetrics.svg +1 -0
- nautobot/project-static/docs/assets/app-icons/icon-CircuitMaintenance.png +0 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js → bundle.ad660dcc.min.js} +6 -6
- nautobot/project-static/docs/assets/javascripts/{bundle.3220b9d7.min.js.map → bundle.ad660dcc.min.js.map} +3 -3
- nautobot/project-static/docs/assets/javascripts/glightbox.min.js +1 -0
- nautobot/project-static/docs/assets/stylesheets/glightbox.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css → main.6543a935.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.66ac8b77.min.css.map → main.6543a935.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +157 -13
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +107 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +97 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +144 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +97 -11
- nautobot/project-static/docs/development/apps/api/configuration-view.html +97 -11
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/global-search.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/graphql.html +97 -11
- nautobot/project-static/docs/development/apps/api/models/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +98 -12
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +97 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +97 -11
- nautobot/project-static/docs/development/apps/api/prometheus.html +97 -11
- nautobot/project-static/docs/development/apps/api/setup.html +97 -11
- nautobot/project-static/docs/development/apps/api/testing.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +97 -11
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/base-template.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/index.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/notes.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +97 -11
- nautobot/project-static/docs/development/apps/api/views/urls.html +97 -11
- nautobot/project-static/docs/development/apps/index.html +97 -11
- nautobot/project-static/docs/development/apps/migration/code-updates.html +98 -12
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +97 -11
- nautobot/project-static/docs/development/apps/migration/from-v1.html +99 -13
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +97 -11
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +97 -11
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +97 -11
- nautobot/project-static/docs/development/core/application-registry.html +98 -12
- nautobot/project-static/docs/development/core/best-practices.html +97 -11
- nautobot/project-static/docs/development/core/bootstrap-ui.html +97 -11
- nautobot/project-static/docs/development/core/caching.html +97 -11
- nautobot/project-static/docs/development/core/controllers.html +97 -11
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +97 -11
- nautobot/project-static/docs/development/core/generic-views.html +97 -11
- nautobot/project-static/docs/development/core/getting-started.html +99 -13
- nautobot/project-static/docs/development/core/homepage.html +97 -11
- nautobot/project-static/docs/development/core/index.html +97 -11
- nautobot/project-static/docs/development/core/model-checklist.html +97 -11
- nautobot/project-static/docs/development/core/model-features.html +97 -11
- nautobot/project-static/docs/development/core/natural-keys.html +97 -11
- nautobot/project-static/docs/development/core/navigation-menu.html +97 -11
- nautobot/project-static/docs/development/core/release-checklist.html +97 -11
- nautobot/project-static/docs/development/core/role-internals.html +97 -11
- nautobot/project-static/docs/development/core/settings.html +97 -11
- nautobot/project-static/docs/development/core/style-guide.html +97 -11
- nautobot/project-static/docs/development/core/templates.html +97 -11
- nautobot/project-static/docs/development/core/testing.html +98 -12
- nautobot/project-static/docs/development/core/user-preferences.html +97 -11
- nautobot/project-static/docs/development/index.html +97 -11
- nautobot/project-static/docs/development/jobs/index.html +97 -11
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +97 -11
- nautobot/project-static/docs/index.html +13 -8362
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +8229 -0
- nautobot/project-static/docs/overview/design_philosophy.html +8158 -0
- nautobot/project-static/docs/overview/index.html +8230 -0
- nautobot/project-static/docs/release-notes/index.html +97 -11
- nautobot/project-static/docs/release-notes/version-1.0.html +98 -12
- nautobot/project-static/docs/release-notes/version-1.1.html +97 -11
- nautobot/project-static/docs/release-notes/version-1.2.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.3.html +98 -12
- nautobot/project-static/docs/release-notes/version-1.4.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.5.html +99 -13
- nautobot/project-static/docs/release-notes/version-1.6.html +626 -160
- nautobot/project-static/docs/release-notes/version-2.0.html +100 -14
- nautobot/project-static/docs/release-notes/version-2.1.html +97 -11
- nautobot/project-static/docs/release-notes/version-2.2.html +546 -107
- nautobot/project-static/docs/requirements.txt +3 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +268 -258
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +99 -13
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +98 -12
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +100 -14
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +97 -11
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +98 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +97 -11
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/index.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +101 -15
- nautobot/project-static/docs/user-guide/administration/installation/services.html +98 -12
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +97 -11
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +97 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +97 -11
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +97 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +97 -11
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +98 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +97 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +97 -30
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +100 -14
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +99 -13
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +98 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +97 -11
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +100 -14
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +98 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +98 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +97 -11
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +97 -11
- nautobot/project-static/docs/user-guide/index.html +100 -14
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +98 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +98 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +102 -15
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +99 -13
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +99 -13
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +98 -44
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +97 -11
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +97 -11
- nautobot/project-static/js/forms.js +2 -1
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/views.py +1 -0
- nautobot/virtualization/forms.py +18 -6
- nautobot/virtualization/templates/virtualization/clustertype.html +2 -2
- nautobot/virtualization/views.py +2 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/METADATA +1 -1
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/RECORD +364 -331
- nautobot/extras/tests/test_git.py +0 -23
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/NOTICE +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/WHEEL +0 -0
- {nautobot-2.2.4.dist-info → nautobot-2.2.6.dist-info}/entry_points.txt +0 -0
|
@@ -6,18 +6,19 @@ import os
|
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from django.core.exceptions import ValidationError
|
|
8
8
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
9
|
-
from django.core.validators import URLValidator
|
|
10
9
|
from django.db import models
|
|
11
10
|
|
|
12
11
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
13
|
-
from nautobot.core.models.fields import AutoSlugField, slugify_dashes_to_underscores
|
|
12
|
+
from nautobot.core.models.fields import AutoSlugField, LaxURLField, slugify_dashes_to_underscores
|
|
14
13
|
from nautobot.core.models.generics import PrimaryModel
|
|
14
|
+
from nautobot.core.models.validators import EnhancedURLValidator
|
|
15
15
|
from nautobot.extras.utils import check_if_key_is_graphql_safe, extras_features
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@extras_features(
|
|
19
19
|
"config_context_owners",
|
|
20
20
|
"export_template_owners",
|
|
21
|
+
"graphql",
|
|
21
22
|
"job_results",
|
|
22
23
|
"webhooks",
|
|
23
24
|
)
|
|
@@ -34,15 +35,16 @@ class GitRepository(PrimaryModel):
|
|
|
34
35
|
slugify_function=slugify_dashes_to_underscores,
|
|
35
36
|
)
|
|
36
37
|
|
|
37
|
-
remote_url =
|
|
38
|
+
remote_url = LaxURLField(
|
|
38
39
|
max_length=CHARFIELD_MAX_LENGTH,
|
|
39
40
|
# For the moment we don't support ssh:// and git:// URLs
|
|
40
41
|
help_text="Only HTTP and HTTPS URLs are presently supported",
|
|
41
|
-
validators=[
|
|
42
|
+
validators=[EnhancedURLValidator(schemes=["http", "https"])],
|
|
42
43
|
)
|
|
43
44
|
branch = models.CharField(
|
|
44
45
|
max_length=CHARFIELD_MAX_LENGTH,
|
|
45
46
|
default="main",
|
|
47
|
+
help_text="Branch, tag, or commit",
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
current_head = models.CharField(
|
nautobot/extras/models/jobs.py
CHANGED
|
@@ -318,6 +318,25 @@ class Job(PrimaryModel):
|
|
|
318
318
|
{"approval_required": "A job that may have sensitive variables cannot be marked as requiring approval"}
|
|
319
319
|
)
|
|
320
320
|
|
|
321
|
+
def save(self, *args, **kwargs):
|
|
322
|
+
"""When a Job is uninstalled, auto-disable all associated JobButtons, JobHooks, and ScheduledJobs."""
|
|
323
|
+
super().save(*args, **kwargs)
|
|
324
|
+
if not self.installed:
|
|
325
|
+
if self.is_job_button_receiver:
|
|
326
|
+
for jb in JobButton.objects.filter(job=self, enabled=True):
|
|
327
|
+
logger.info("Disabling JobButton %s derived from %s", jb, self)
|
|
328
|
+
jb.enabled = False
|
|
329
|
+
jb.save()
|
|
330
|
+
if self.is_job_hook_receiver:
|
|
331
|
+
for jh in JobHook.objects.filter(job=self, enabled=True):
|
|
332
|
+
logger.info("Disabling JobHook %s derived from %s", jh, self)
|
|
333
|
+
jh.enabled = False
|
|
334
|
+
jh.save()
|
|
335
|
+
for sj in ScheduledJob.objects.filter(job_model=self, enabled=True):
|
|
336
|
+
logger.info("Disabling ScheduledJob %s derived from %s", sj, self)
|
|
337
|
+
sj.enabled = False
|
|
338
|
+
sj.save()
|
|
339
|
+
|
|
321
340
|
|
|
322
341
|
@extras_features("graphql")
|
|
323
342
|
class JobHook(OrganizationalModel):
|
|
@@ -363,6 +382,9 @@ class JobHook(OrganizationalModel):
|
|
|
363
382
|
if not self.type_create and not self.type_delete and not self.type_update:
|
|
364
383
|
raise ValidationError("You must select at least one type: create, update, and/or delete.")
|
|
365
384
|
|
|
385
|
+
if self.enabled and not (self.job.installed and self.job.enabled):
|
|
386
|
+
raise ValidationError({"enabled": "The selected Job is not installed and enabled"})
|
|
387
|
+
|
|
366
388
|
@classmethod
|
|
367
389
|
def check_for_conflicts(
|
|
368
390
|
cls, instance=None, content_types=None, job=None, type_create=None, type_update=None, type_delete=None
|
|
@@ -375,6 +397,7 @@ class JobHook(OrganizationalModel):
|
|
|
375
397
|
"""
|
|
376
398
|
|
|
377
399
|
conflicts = {}
|
|
400
|
+
|
|
378
401
|
job_hook_error_msg = "A job hook already exists for {action} on {content_type} to job {job}"
|
|
379
402
|
|
|
380
403
|
if instance is not None and instance.present_in_database:
|
|
@@ -783,6 +806,7 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
|
|
|
783
806
|
help_text="The object type(s) to which this job button applies.",
|
|
784
807
|
)
|
|
785
808
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
|
|
809
|
+
enabled = models.BooleanField(default=True)
|
|
786
810
|
text = models.CharField(
|
|
787
811
|
max_length=500,
|
|
788
812
|
help_text="Jinja2 template code for button text. Reference the object as <code>{{ obj }}</code> such as <code>{{ obj.platform.name }}</code>. Buttons which render as empty text will not be displayed.",
|
|
@@ -817,6 +841,12 @@ class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
|
|
|
817
841
|
def __str__(self):
|
|
818
842
|
return self.name
|
|
819
843
|
|
|
844
|
+
def clean(self):
|
|
845
|
+
super().clean()
|
|
846
|
+
|
|
847
|
+
if self.enabled and not (self.job.installed and self.job.enabled):
|
|
848
|
+
raise ValidationError({"enabled": "The selected Job is not installed and enabled"})
|
|
849
|
+
|
|
820
850
|
|
|
821
851
|
class ScheduledJobs(models.Model):
|
|
822
852
|
"""Helper table for tracking updates to scheduled tasks.
|
nautobot/extras/models/models.py
CHANGED
|
@@ -22,9 +22,8 @@ from rest_framework.utils.encoders import JSONEncoder
|
|
|
22
22
|
|
|
23
23
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
24
24
|
from nautobot.core.models import BaseManager, BaseModel
|
|
25
|
-
from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName
|
|
25
|
+
from nautobot.core.models.fields import ForeignKeyWithAutoRelatedName, LaxURLField
|
|
26
26
|
from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
|
|
27
|
-
from nautobot.core.models.validators import EnhancedURLValidator
|
|
28
27
|
from nautobot.core.utils.data import deepmerge, render_jinja2
|
|
29
28
|
from nautobot.extras.choices import (
|
|
30
29
|
ButtonClassChoices,
|
|
@@ -442,10 +441,9 @@ class ExternalIntegration(PrimaryModel):
|
|
|
442
441
|
"""Model for tracking integrations with external applications."""
|
|
443
442
|
|
|
444
443
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
|
|
445
|
-
remote_url =
|
|
444
|
+
remote_url = LaxURLField(
|
|
446
445
|
max_length=500,
|
|
447
446
|
verbose_name="Remote URL",
|
|
448
|
-
validators=[EnhancedURLValidator()],
|
|
449
447
|
)
|
|
450
448
|
secrets_group = models.ForeignKey(
|
|
451
449
|
null=True,
|
nautobot/extras/signals.py
CHANGED
|
@@ -246,7 +246,12 @@ def _handle_deleted_object(sender, instance, **kwargs):
|
|
|
246
246
|
|
|
247
247
|
if save_new_objectchange:
|
|
248
248
|
change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
|
|
249
|
-
{
|
|
249
|
+
{
|
|
250
|
+
"action": ObjectChangeActionChoices.ACTION_DELETE,
|
|
251
|
+
"instance": instance,
|
|
252
|
+
"user": user,
|
|
253
|
+
"changed_object_id": instance.pk,
|
|
254
|
+
}
|
|
250
255
|
)
|
|
251
256
|
if not change_context.defer_object_changes:
|
|
252
257
|
objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
|
nautobot/extras/tables.py
CHANGED
|
@@ -818,6 +818,7 @@ class JobButtonTable(BaseTable):
|
|
|
818
818
|
pk = ToggleColumn()
|
|
819
819
|
name = tables.Column(linkify=True)
|
|
820
820
|
job = tables.Column(linkify=True)
|
|
821
|
+
enabled = BooleanColumn()
|
|
821
822
|
confirmation = BooleanColumn()
|
|
822
823
|
content_types = ContentTypesColumn(truncate_words=15)
|
|
823
824
|
|
|
@@ -829,6 +830,7 @@ class JobButtonTable(BaseTable):
|
|
|
829
830
|
"content_types",
|
|
830
831
|
"text",
|
|
831
832
|
"job",
|
|
833
|
+
"enabled",
|
|
832
834
|
"group_name",
|
|
833
835
|
"weight",
|
|
834
836
|
"button_class",
|
|
@@ -841,6 +843,7 @@ class JobButtonTable(BaseTable):
|
|
|
841
843
|
"group_name",
|
|
842
844
|
"weight",
|
|
843
845
|
"job",
|
|
846
|
+
"enabled",
|
|
844
847
|
"confirmation",
|
|
845
848
|
)
|
|
846
849
|
|
|
@@ -32,19 +32,23 @@
|
|
|
32
32
|
</tr>
|
|
33
33
|
<tr>
|
|
34
34
|
<td>Text</td>
|
|
35
|
-
<td><
|
|
35
|
+
<td><pre>{{ object.text }}</pre></td>
|
|
36
36
|
</tr>
|
|
37
37
|
<tr>
|
|
38
38
|
<td>Job</td>
|
|
39
39
|
<td><a href="{% url 'extras:job_run_by_class_path' class_path=object.job.class_path %}">{{ object.job }}</a></td>
|
|
40
40
|
</tr>
|
|
41
|
+
<tr>
|
|
42
|
+
<td>Enabled</td>
|
|
43
|
+
<td>{{ object.enabled|render_boolean }}</td>
|
|
44
|
+
</tr>
|
|
41
45
|
<tr>
|
|
42
46
|
<td>Button Class</td>
|
|
43
47
|
<td><button class="btn btn-{{ object.button_class }}">{{ object.button_class | title }}</button></td>
|
|
44
48
|
</tr>
|
|
45
49
|
<tr>
|
|
46
50
|
<td>Confirmation</td>
|
|
47
|
-
<td
|
|
51
|
+
<td>{{ object.confirmation|render_boolean}}</td>
|
|
48
52
|
</tr>
|
|
49
53
|
</table>
|
|
50
54
|
</div>
|
|
@@ -127,7 +127,7 @@ def _render_job_button_for_obj(job_button, obj, context, content_type):
|
|
|
127
127
|
"object": obj,
|
|
128
128
|
"job": job_button.job,
|
|
129
129
|
"hidden_inputs": hidden_inputs,
|
|
130
|
-
"disabled": "" if has_run_perm else "disabled",
|
|
130
|
+
"disabled": "" if (has_run_perm and job_button.job.installed and job_button.job.enabled) else "disabled",
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
if job_button.confirmation:
|
|
@@ -149,7 +149,7 @@ def job_buttons(context, obj):
|
|
|
149
149
|
"""
|
|
150
150
|
content_type = ContentType.objects.get_for_model(obj)
|
|
151
151
|
# We will enforce "run" permission later in deciding which buttons to show as disabled.
|
|
152
|
-
buttons = JobButton.objects.filter(content_types=content_type)
|
|
152
|
+
buttons = JobButton.objects.filter(content_types=content_type, enabled=True)
|
|
153
153
|
if not buttons:
|
|
154
154
|
return SAFE_EMPTY_STR
|
|
155
155
|
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
_metadata:
|
|
3
|
+
name: "Config Context Schema 1"
|
|
4
|
+
description: "Schema for defining first names, last names, and ages."
|
|
5
|
+
data_schema:
|
|
6
|
+
title: "Person"
|
|
7
|
+
type: "object"
|
|
8
|
+
properties:
|
|
9
|
+
firstName:
|
|
10
|
+
type: "string"
|
|
11
|
+
description: "The person's first name."
|
|
12
|
+
lastName:
|
|
13
|
+
type: "string"
|
|
14
|
+
description: "The person's last name."
|
|
15
|
+
age:
|
|
16
|
+
description: "Age in years which must be equal to or greater than zero."
|
|
17
|
+
type: "integer"
|
|
18
|
+
minimum: 0
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
_metadata:
|
|
3
|
+
name: "Frobozz 1000 NTP servers"
|
|
4
|
+
weight: 1500
|
|
5
|
+
description: "NTP servers for Frobozz 1000 devices **only**"
|
|
6
|
+
is_active: true
|
|
7
|
+
config_context_schema: "Config Context Schema 1"
|
|
8
|
+
device_types:
|
|
9
|
+
- model: "Frobozz 1000"
|
|
10
|
+
ntp-servers:
|
|
11
|
+
- "172.16.10.22"
|
|
12
|
+
- "172.16.10.33"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from nautobot.extras.jobs import Job, JobButtonReceiver, JobHookReceiver
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MyJob(Job):
|
|
5
|
+
def run(self):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MyJobHookReceiver(JobHookReceiver):
|
|
10
|
+
def receive_job_hook(self, change, action, changed_object):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MyJobButtonReceiver(JobButtonReceiver):
|
|
15
|
+
def receive_job_button(self, obj):
|
|
16
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import nosuchmodule
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
print(
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import os.path
|
|
6
|
+
import shutil
|
|
7
|
+
import tempfile
|
|
8
|
+
|
|
9
|
+
from git import Repo
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SOURCE_DIR = os.path.join(os.path.dirname(__file__), "git_data")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_and_populate_git_repository(target_path):
|
|
18
|
+
"""
|
|
19
|
+
Create a Git repository in `target_path` and populate it with commits and tags based on contents of `SOURCE_DIR`.
|
|
20
|
+
|
|
21
|
+
An initial commit will always be created, containing no tracked files, and will be given the tag `empty-repo`.
|
|
22
|
+
After that, each subdir under `SOURCE_DIR` will be used as the basis for a single commit to the repo, so given:
|
|
23
|
+
|
|
24
|
+
nautobot/extras/tests/git_data
|
|
25
|
+
├── 01-valid-files
|
|
26
|
+
│ ├── config_context_schemas
|
|
27
|
+
│ │ └── schema-1.yaml
|
|
28
|
+
│ └── config_contexts
|
|
29
|
+
│ └── context.yaml
|
|
30
|
+
└── 02-invalid-files
|
|
31
|
+
└── config_context_schemas
|
|
32
|
+
├── badschema1.json
|
|
33
|
+
└── badschema2.json
|
|
34
|
+
|
|
35
|
+
...after the initial empty commit, the next commit would contain files `config_context_schemas/schema-1.yaml` and
|
|
36
|
+
`config_contexts/context.yaml`, and would be given the tag `valid-files`. The next commit would remove those files
|
|
37
|
+
but add the files `config_context_schemas/badschema1.json` and `config_context_schemas/badschema2.json`, and would
|
|
38
|
+
be tagged as `invalid-files`.
|
|
39
|
+
|
|
40
|
+
Note that each commit is fully defined by the files in the appropriate subdirectory; if you want a file to exist
|
|
41
|
+
across multiple separate commits, it must exist in multiple subdirectories. Use of symlinks is encouraged in such
|
|
42
|
+
a scenario.
|
|
43
|
+
"""
|
|
44
|
+
os.makedirs(target_path, exist_ok=True)
|
|
45
|
+
repo = Repo.init(target_path, initial_branch="main")
|
|
46
|
+
repo.config_writer().set_value("user", "name", "Nautobot Automation").release()
|
|
47
|
+
repo.config_writer().set_value("user", "email", "nautobot@nautobot.com").release()
|
|
48
|
+
|
|
49
|
+
repo.index.commit("Empty commit")
|
|
50
|
+
repo.create_tag("empty-repo", message="Nothing here yet")
|
|
51
|
+
|
|
52
|
+
# Create a commit matching each subdirectory in the git_data directory
|
|
53
|
+
for dirname in sorted(os.listdir(SOURCE_DIR)):
|
|
54
|
+
# Clean up from any previous commit
|
|
55
|
+
for root, _, files in os.walk(target_path):
|
|
56
|
+
if ".git" in root:
|
|
57
|
+
continue
|
|
58
|
+
for filename in files:
|
|
59
|
+
repo.index.remove([os.path.join(root, filename)])
|
|
60
|
+
os.remove(os.path.join(root, filename))
|
|
61
|
+
|
|
62
|
+
shutil.copytree(os.path.join(SOURCE_DIR, dirname), target_path, dirs_exist_ok=True)
|
|
63
|
+
for root, _, files in os.walk(target_path):
|
|
64
|
+
if ".git" in root:
|
|
65
|
+
continue
|
|
66
|
+
for filename in files:
|
|
67
|
+
repo.index.add([os.path.join(root, filename)])
|
|
68
|
+
repo.index.commit(dirname)
|
|
69
|
+
# Directory "01-valid-files" --> tag "valid-files" so that we won't break tests if we renumber the directories
|
|
70
|
+
repo.create_tag(dirname[3:], message=f"Tag based on {dirname} files")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
directory_path = tempfile.TemporaryDirectory().name # pylint: disable=consider-using-with
|
|
75
|
+
print(f"Creating test Git repository in {directory_path}...")
|
|
76
|
+
create_and_populate_git_repository(directory_path)
|
|
@@ -17,6 +17,7 @@ from nautobot.core.models.fields import slugify_dashes_to_underscores
|
|
|
17
17
|
from nautobot.core.testing import APITestCase, APIViewTestCases
|
|
18
18
|
from nautobot.core.testing.utils import disable_warnings
|
|
19
19
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
20
|
+
from nautobot.core.utils.permissions import get_permission_for_model
|
|
20
21
|
from nautobot.dcim.models import (
|
|
21
22
|
Controller,
|
|
22
23
|
Device,
|
|
@@ -768,7 +769,7 @@ class DynamicGroupTestMixin:
|
|
|
768
769
|
|
|
769
770
|
# Then the DynamicGroups.
|
|
770
771
|
cls.content_type = ContentType.objects.get_for_model(Device)
|
|
771
|
-
cls.groups =
|
|
772
|
+
cls.groups = [
|
|
772
773
|
DynamicGroup.objects.create(
|
|
773
774
|
name="API DynamicGroup 1",
|
|
774
775
|
content_type=cls.content_type,
|
|
@@ -811,13 +812,34 @@ class DynamicGroupTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
|
|
|
811
812
|
def test_get_members(self):
|
|
812
813
|
"""Test that the `/members/` API endpoint returns what is expected."""
|
|
813
814
|
self.add_permissions("extras.view_dynamicgroup")
|
|
814
|
-
instance =
|
|
815
|
+
instance = self.groups[0]
|
|
816
|
+
self.add_permissions(get_permission_for_model(instance.content_type.model_class(), "view"))
|
|
815
817
|
member_count = instance.members.count()
|
|
816
818
|
url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
|
|
817
819
|
response = self.client.get(url, **self.header)
|
|
818
820
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
819
821
|
self.assertEqual(member_count, len(response.json()["results"]))
|
|
820
822
|
|
|
823
|
+
def test_get_members_with_constrained_permission(self):
|
|
824
|
+
"""Test that the `/members/` API endpoint enforces permissions on the member model."""
|
|
825
|
+
self.add_permissions("extras.view_dynamicgroup")
|
|
826
|
+
instance = self.groups[0]
|
|
827
|
+
obj1 = instance.members.first()
|
|
828
|
+
obj_perm = ObjectPermission(
|
|
829
|
+
name="Test permission",
|
|
830
|
+
constraints={"pk__in": [obj1.pk]},
|
|
831
|
+
actions=["view"],
|
|
832
|
+
)
|
|
833
|
+
obj_perm.save()
|
|
834
|
+
obj_perm.users.add(self.user)
|
|
835
|
+
obj_perm.object_types.add(instance.content_type)
|
|
836
|
+
|
|
837
|
+
url = reverse("extras-api:dynamicgroup-members", kwargs={"pk": instance.pk})
|
|
838
|
+
response = self.client.get(url, **self.header)
|
|
839
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
840
|
+
self.assertEqual(len(response.json()["results"]), 1)
|
|
841
|
+
self.assertEqual(response.json()["results"][0]["id"], str(obj1.pk))
|
|
842
|
+
|
|
821
843
|
|
|
822
844
|
class DynamicGroupMembershipTest(DynamicGroupTestMixin, APIViewTestCases.APIViewTestCase):
|
|
823
845
|
model = DynamicGroupMembership
|
|
@@ -2023,46 +2045,56 @@ class JobHookTest(APIViewTestCases.APIViewTestCase):
|
|
|
2023
2045
|
|
|
2024
2046
|
@classmethod
|
|
2025
2047
|
def setUpTestData(cls):
|
|
2048
|
+
jhr_log = Job.objects.get(job_class_name="TestJobHookReceiverLog")
|
|
2049
|
+
jhr_log.enabled = True
|
|
2050
|
+
jhr_log.save()
|
|
2051
|
+
jhr_change = Job.objects.get(job_class_name="TestJobHookReceiverChange")
|
|
2052
|
+
jhr_change.enabled = True
|
|
2053
|
+
jhr_change.save()
|
|
2054
|
+
jhr_fail = Job.objects.get(job_class_name="TestJobHookReceiverFail")
|
|
2055
|
+
jhr_fail.enabled = True
|
|
2056
|
+
jhr_fail.save()
|
|
2057
|
+
|
|
2026
2058
|
cls.create_data = [
|
|
2027
2059
|
{
|
|
2028
2060
|
"name": "JobHook4",
|
|
2029
2061
|
"content_types": ["dcim.consoleport"],
|
|
2030
2062
|
"type_delete": True,
|
|
2031
|
-
"job":
|
|
2063
|
+
"job": jhr_log.pk,
|
|
2032
2064
|
"enabled": False,
|
|
2033
2065
|
},
|
|
2034
2066
|
{
|
|
2035
2067
|
"name": "JobHook5",
|
|
2036
2068
|
"content_types": ["dcim.consoleport"],
|
|
2037
2069
|
"type_delete": True,
|
|
2038
|
-
"job":
|
|
2070
|
+
"job": jhr_change.pk,
|
|
2039
2071
|
"enabled": False,
|
|
2040
2072
|
},
|
|
2041
2073
|
{
|
|
2042
2074
|
"name": "JobHook6",
|
|
2043
2075
|
"content_types": ["dcim.consoleport"],
|
|
2044
2076
|
"type_delete": True,
|
|
2045
|
-
"job":
|
|
2077
|
+
"job": jhr_fail.pk,
|
|
2046
2078
|
"enabled": False,
|
|
2047
2079
|
},
|
|
2048
2080
|
]
|
|
2049
2081
|
cls.job_hooks = (
|
|
2050
2082
|
JobHook(
|
|
2051
2083
|
name="JobHook1",
|
|
2084
|
+
job=jhr_log,
|
|
2052
2085
|
type_create=True,
|
|
2053
|
-
job=Job.objects.get(job_class_name="TestJobHookReceiverLog"),
|
|
2054
2086
|
type_delete=True,
|
|
2055
2087
|
),
|
|
2056
2088
|
JobHook(
|
|
2057
2089
|
name="JobHook2",
|
|
2090
|
+
job=jhr_change,
|
|
2058
2091
|
type_create=True,
|
|
2059
|
-
job=Job.objects.get(job_class_name="TestJobHookReceiverChange"),
|
|
2060
2092
|
type_delete=True,
|
|
2061
2093
|
),
|
|
2062
2094
|
JobHook(
|
|
2063
2095
|
name="JobHook3",
|
|
2096
|
+
job=jhr_fail,
|
|
2064
2097
|
type_create=True,
|
|
2065
|
-
job=Job.objects.get(job_class_name="TestJobHookReceiverFail"),
|
|
2066
2098
|
type_delete=True,
|
|
2067
2099
|
),
|
|
2068
2100
|
)
|
|
@@ -2116,18 +2148,25 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
|
|
|
2116
2148
|
|
|
2117
2149
|
@classmethod
|
|
2118
2150
|
def setUpTestData(cls):
|
|
2151
|
+
jbr_simple = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
|
|
2152
|
+
jbr_simple.enabled = True
|
|
2153
|
+
jbr_simple.save()
|
|
2154
|
+
jbr_complex = Job.objects.get(job_class_name="TestJobButtonReceiverComplex")
|
|
2155
|
+
jbr_complex.enabled = True
|
|
2156
|
+
jbr_complex.save()
|
|
2157
|
+
|
|
2119
2158
|
cls.create_data = [
|
|
2120
2159
|
{
|
|
2121
2160
|
"name": "JobButton4",
|
|
2122
2161
|
"text": "JobButton4",
|
|
2123
2162
|
"content_types": ["dcim.location"],
|
|
2124
|
-
"job":
|
|
2163
|
+
"job": jbr_simple.pk,
|
|
2125
2164
|
},
|
|
2126
2165
|
{
|
|
2127
2166
|
"name": "JobButton5",
|
|
2128
2167
|
"text": "JobButton5",
|
|
2129
2168
|
"content_types": ["circuits.circuit"],
|
|
2130
|
-
"job":
|
|
2169
|
+
"job": jbr_complex.pk,
|
|
2131
2170
|
},
|
|
2132
2171
|
]
|
|
2133
2172
|
location_type = ContentType.objects.get_for_model(Location)
|
|
@@ -2136,7 +2175,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
|
|
|
2136
2175
|
location_jb = JobButton(
|
|
2137
2176
|
name="api-test-location",
|
|
2138
2177
|
text="API job button location text",
|
|
2139
|
-
job=
|
|
2178
|
+
job=jbr_simple,
|
|
2140
2179
|
weight=100,
|
|
2141
2180
|
confirmation=True,
|
|
2142
2181
|
)
|
|
@@ -2146,7 +2185,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
|
|
|
2146
2185
|
device_jb = JobButton.objects.create(
|
|
2147
2186
|
name="api-test-device",
|
|
2148
2187
|
text="API job button device text",
|
|
2149
|
-
job=
|
|
2188
|
+
job=jbr_simple,
|
|
2150
2189
|
weight=100,
|
|
2151
2190
|
confirmation=True,
|
|
2152
2191
|
)
|
|
@@ -2156,7 +2195,7 @@ class JobButtonTest(APIViewTestCases.APIViewTestCase):
|
|
|
2156
2195
|
complex_jb = JobButton.objects.create(
|
|
2157
2196
|
name="api-test-complex",
|
|
2158
2197
|
text="API job button complex text",
|
|
2159
|
-
job=
|
|
2198
|
+
job=jbr_complex,
|
|
2160
2199
|
weight=100,
|
|
2161
2200
|
confirmation=True,
|
|
2162
2201
|
)
|
|
@@ -5,7 +5,16 @@ from django.test import TestCase
|
|
|
5
5
|
from nautobot.core.celery import app
|
|
6
6
|
from nautobot.core.testing import TransactionTestCase
|
|
7
7
|
from nautobot.core.utils.lookup import get_changes_for_model
|
|
8
|
-
from nautobot.dcim.models import
|
|
8
|
+
from nautobot.dcim.models import (
|
|
9
|
+
DeviceType,
|
|
10
|
+
DeviceTypeToSoftwareImageFile,
|
|
11
|
+
Location,
|
|
12
|
+
LocationType,
|
|
13
|
+
Manufacturer,
|
|
14
|
+
Platform,
|
|
15
|
+
SoftwareImageFile,
|
|
16
|
+
SoftwareVersion,
|
|
17
|
+
)
|
|
9
18
|
from nautobot.extras.choices import ObjectChangeActionChoices, ObjectChangeEventContextChoices
|
|
10
19
|
from nautobot.extras.context_managers import (
|
|
11
20
|
deferred_change_logging_for_bulk_operation,
|
|
@@ -294,6 +303,29 @@ class BulkEditDeleteChangeLogging(TestCase):
|
|
|
294
303
|
self.assertIsNone(snapshots["differences"]["removed"])
|
|
295
304
|
self.assertEqual(snapshots["differences"]["added"]["description"], "changed")
|
|
296
305
|
|
|
306
|
+
def test_bulk_edit_device_type_software_image_file(self):
|
|
307
|
+
"""Test that bulk edits to null does not cause integrity error"""
|
|
308
|
+
manufacturer = Manufacturer.objects.create(name="Test")
|
|
309
|
+
platform = Platform.objects.create(name="Test")
|
|
310
|
+
software_status = Status.objects.get_for_model(SoftwareVersion).first()
|
|
311
|
+
software_version = SoftwareVersion.objects.create(version="1.0.0", platform=platform, status=software_status)
|
|
312
|
+
software_image_file = SoftwareImageFile.objects.create(
|
|
313
|
+
image_file_name="test.iso", software_version=software_version, status=software_status
|
|
314
|
+
)
|
|
315
|
+
device_type = DeviceType.objects.create(manufacturer=manufacturer, model="test123")
|
|
316
|
+
device_type.software_image_files.set([software_image_file])
|
|
317
|
+
with web_request_context(self.user):
|
|
318
|
+
with deferred_change_logging_for_bulk_operation():
|
|
319
|
+
device_type.software_image_files.set([])
|
|
320
|
+
device_type.save()
|
|
321
|
+
|
|
322
|
+
oc_list = get_changes_for_model(DeviceTypeToSoftwareImageFile)
|
|
323
|
+
self.assertEqual(len(oc_list), 1)
|
|
324
|
+
self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_DELETE)
|
|
325
|
+
self.assertIsNotNone(oc_list[0].changed_object_id)
|
|
326
|
+
self.assertEqual(oc_list[0].user, self.user)
|
|
327
|
+
self.assertEqual(oc_list[0].user_name, self.user.username)
|
|
328
|
+
|
|
297
329
|
def test_change_log_context(self):
|
|
298
330
|
location_type = LocationType.objects.get(name="Campus")
|
|
299
331
|
location_status = Status.objects.get_for_model(Location).first()
|