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
nautobot/core/testing/views.py
CHANGED
|
@@ -213,6 +213,8 @@ class ViewTestCases:
|
|
|
213
213
|
escape(str(instance.cf.get(custom_field.key) or "")), response_body, msg=response_body
|
|
214
214
|
)
|
|
215
215
|
|
|
216
|
+
return response # for consumption by child test cases if desired
|
|
217
|
+
|
|
216
218
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
217
219
|
def test_get_object_with_constrained_permission(self):
|
|
218
220
|
instance1, instance2 = self._get_queryset().all()[:2]
|
|
@@ -230,11 +232,14 @@ class ViewTestCases:
|
|
|
230
232
|
obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
|
|
231
233
|
|
|
232
234
|
# Try GET to permitted object
|
|
233
|
-
self.
|
|
235
|
+
response = self.client.get(instance1.get_absolute_url())
|
|
236
|
+
self.assertHttpStatus(response, 200)
|
|
234
237
|
|
|
235
238
|
# Try GET to non-permitted object
|
|
236
239
|
self.assertHttpStatus(self.client.get(instance2.get_absolute_url()), 404)
|
|
237
240
|
|
|
241
|
+
return response # for consumption by child test cases if desired
|
|
242
|
+
|
|
238
243
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
239
244
|
def test_has_advanced_tab(self):
|
|
240
245
|
instance = self._get_queryset().first()
|
|
@@ -740,6 +745,13 @@ class ViewTestCases:
|
|
|
740
745
|
def get_list_view(self):
|
|
741
746
|
return lookup.get_view_for_model(self.model, view_type="List")
|
|
742
747
|
|
|
748
|
+
def test_list_view_has_filter_form(self):
|
|
749
|
+
view = self.get_list_view()
|
|
750
|
+
if hasattr(view, "filterset_form"): # ObjectListView
|
|
751
|
+
self.assertIsNotNone(view.filterset_form, "List view lacks a FilterForm")
|
|
752
|
+
if hasattr(view, "filterset_form_class"): # ObjectListViewMixin
|
|
753
|
+
self.assertIsNotNone(view.filterset_form_class, "List viewset lacks a FilterForm")
|
|
754
|
+
|
|
743
755
|
def test_table_with_indentation_is_removed_on_filter_or_sort(self):
|
|
744
756
|
self.user.is_superuser = True
|
|
745
757
|
self.user.save()
|
|
@@ -4,6 +4,7 @@ from django import forms as django_forms
|
|
|
4
4
|
from django.apps import apps
|
|
5
5
|
from django.contrib.auth.models import Group
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
|
+
from django.core.exceptions import ValidationError
|
|
7
8
|
from django.db.models import Q
|
|
8
9
|
from django.http import QueryDict
|
|
9
10
|
from django.test import TestCase
|
|
@@ -11,7 +12,7 @@ from django.test import TestCase
|
|
|
11
12
|
from nautobot.circuits import models as circuits_models
|
|
12
13
|
from nautobot.core import exceptions, forms, settings_funcs
|
|
13
14
|
from nautobot.core.api import utils as api_utils
|
|
14
|
-
from nautobot.core.models import fields as core_fields, utils as models_utils
|
|
15
|
+
from nautobot.core.models import fields as core_fields, utils as models_utils, validators
|
|
15
16
|
from nautobot.core.utils import data as data_utils, filtering, lookup, requests
|
|
16
17
|
from nautobot.core.utils.migrations import update_object_change_ct_for_replaced_models
|
|
17
18
|
from nautobot.dcim import filters as dcim_filters, forms as dcim_forms, models as dcim_models, tables
|
|
@@ -372,6 +373,52 @@ class SlugifyFunctionsTest(TestCase):
|
|
|
372
373
|
self.assertEqual(core_fields.slugify_dashes_to_underscores(content), expected)
|
|
373
374
|
|
|
374
375
|
|
|
376
|
+
class LaxURLFieldTest(TestCase):
|
|
377
|
+
"""Test LaxURLField and related functionality."""
|
|
378
|
+
|
|
379
|
+
VALID_URLS = [
|
|
380
|
+
"http://example.com",
|
|
381
|
+
"https://local-dns/foo/bar.git", # not supported out-of-the-box by Django, hence our custom classes
|
|
382
|
+
"https://1.1.1.1:8080/",
|
|
383
|
+
"https://[2001:db8::]/",
|
|
384
|
+
]
|
|
385
|
+
INVALID_URLS = [
|
|
386
|
+
"unknown://example.com/",
|
|
387
|
+
"foo:/",
|
|
388
|
+
"http://file://",
|
|
389
|
+
]
|
|
390
|
+
|
|
391
|
+
def test_enhanced_url_validator(self):
|
|
392
|
+
for valid in self.VALID_URLS:
|
|
393
|
+
with self.subTest(valid=valid):
|
|
394
|
+
validators.EnhancedURLValidator()(valid)
|
|
395
|
+
|
|
396
|
+
for invalid in self.INVALID_URLS:
|
|
397
|
+
with self.subTest(invalid=invalid):
|
|
398
|
+
with self.assertRaises(django_forms.ValidationError):
|
|
399
|
+
validators.EnhancedURLValidator()(invalid)
|
|
400
|
+
|
|
401
|
+
def test_forms_lax_url_field(self):
|
|
402
|
+
for valid in self.VALID_URLS:
|
|
403
|
+
with self.subTest(valid=valid):
|
|
404
|
+
forms.LaxURLField().clean(valid)
|
|
405
|
+
|
|
406
|
+
for invalid in self.INVALID_URLS:
|
|
407
|
+
with self.subTest(invalid=invalid):
|
|
408
|
+
with self.assertRaises(django_forms.ValidationError):
|
|
409
|
+
forms.LaxURLField().clean(invalid)
|
|
410
|
+
|
|
411
|
+
def test_models_lax_url_field(self):
|
|
412
|
+
for valid in self.VALID_URLS:
|
|
413
|
+
with self.subTest(valid=valid):
|
|
414
|
+
core_fields.LaxURLField().run_validators(valid)
|
|
415
|
+
|
|
416
|
+
for invalid in self.INVALID_URLS:
|
|
417
|
+
with self.subTest(invalid=invalid):
|
|
418
|
+
with self.assertRaises(ValidationError):
|
|
419
|
+
core_fields.LaxURLField().run_validators(invalid)
|
|
420
|
+
|
|
421
|
+
|
|
375
422
|
class LookupRelatedFunctionTest(TestCase):
|
|
376
423
|
def test_is_single_choice_field(self):
|
|
377
424
|
"""
|
nautobot/core/utils/git.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from collections import namedtuple
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
import string
|
|
6
7
|
|
|
7
8
|
from git import Repo
|
|
8
9
|
|
|
@@ -34,8 +35,8 @@ GIT_ENVIRONMENT = {
|
|
|
34
35
|
|
|
35
36
|
def swap_status_initials(data):
|
|
36
37
|
"""Swap Git status initials with its equivalent."""
|
|
37
|
-
initial, text = data.split("\t")
|
|
38
|
-
return GitDiffLog(status=GIT_STATUS_MAP.get(initial), text=text)
|
|
38
|
+
initial, text = data.split("\t", 1)
|
|
39
|
+
return GitDiffLog(status=GIT_STATUS_MAP.get(initial[0]), text=text)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def convert_git_diff_log_to_list(logs):
|
|
@@ -93,69 +94,140 @@ class GitRepo:
|
|
|
93
94
|
"""
|
|
94
95
|
Check out the given branch, and optionally the specified commit within that branch.
|
|
95
96
|
|
|
97
|
+
Args:
|
|
98
|
+
branch (str): A branch name, a tag name, or a (possibly abbreviated) commit identifier.
|
|
99
|
+
commit_hexsha (str): A specific (possibly abbreviated) commit identifier.
|
|
100
|
+
|
|
101
|
+
If `commit_hexsha` is specified and `branch` is either a tag or a commit identifier, they must match.
|
|
102
|
+
If `commit_hexsha` is specified and `branch` is a branch name, it must contain the specified commit.
|
|
103
|
+
|
|
96
104
|
Returns:
|
|
97
|
-
(str, bool): commit_hexsha the repo contains now, whether any change occurred
|
|
105
|
+
(str, bool): commit_hexsha the repo contains now, and whether any change occurred
|
|
98
106
|
"""
|
|
99
107
|
# Short-circuit logic - do we already have this commit checked out?
|
|
100
|
-
if commit_hexsha and
|
|
108
|
+
if commit_hexsha and self.head.startswith(commit_hexsha):
|
|
101
109
|
logger.debug(f"Commit {commit_hexsha} is already checked out.")
|
|
102
|
-
return (
|
|
110
|
+
return (self.head, False)
|
|
111
|
+
# User might specify the commit as a "branch" name...
|
|
112
|
+
if not commit_hexsha and set(branch).issubset(string.hexdigits) and self.head.startswith(branch):
|
|
113
|
+
logger.debug("Commit %s is already checked out.", branch)
|
|
114
|
+
return (self.head, False)
|
|
103
115
|
|
|
104
116
|
self.fetch()
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
# Is `branch` actually a branch, a tag, or a commit? Heuristics:
|
|
118
|
+
is_branch = branch in self.repo.remotes.origin.refs
|
|
119
|
+
is_tag = branch in self.repo.tags
|
|
120
|
+
maybe_commit = set(branch).issubset(string.hexdigits)
|
|
121
|
+
logger.debug(
|
|
122
|
+
"Branch %s --> is_branch: %s, is_tag: %s, maybe_commit: %s",
|
|
123
|
+
branch,
|
|
124
|
+
is_branch,
|
|
125
|
+
is_tag,
|
|
126
|
+
maybe_commit,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if is_branch:
|
|
130
|
+
if commit_hexsha:
|
|
131
|
+
# Sanity check - GitPython doesn't provide a handy API for this so we just call a raw Git command:
|
|
132
|
+
# $ git branch origin/<branch> --remotes --contains <commit>
|
|
133
|
+
# prints the branch name if it DOES contain the commit, and nothing if it DOES NOT contain the commit.
|
|
134
|
+
# Since we did a `fetch` and not a `pull` above, we need to check for the commit in the remote origin
|
|
135
|
+
# branch, not the local (not-yet-updated) branch.
|
|
136
|
+
if branch not in self.repo.git.branch(f"origin/{branch}", "--remotes", "--contains", commit_hexsha):
|
|
137
|
+
raise RuntimeError(
|
|
138
|
+
f"Requested to check out commit {commit_hexsha}, but it's not part of branch {branch}!"
|
|
139
|
+
)
|
|
140
|
+
logger.info("Checking out commit %s on branch %s...", commit_hexsha, branch)
|
|
141
|
+
self.repo.git.checkout(commit_hexsha)
|
|
142
|
+
return (self.head, True)
|
|
143
|
+
|
|
144
|
+
if branch in self.repo.heads:
|
|
145
|
+
branch_head = self.repo.heads[branch]
|
|
146
|
+
else:
|
|
147
|
+
try:
|
|
148
|
+
branch_head = self.repo.create_head(branch, self.repo.remotes.origin.refs[branch])
|
|
149
|
+
branch_head.set_tracking_branch(self.repo.remotes.origin.refs[branch])
|
|
150
|
+
except IndexError as git_error:
|
|
151
|
+
logger.error(
|
|
152
|
+
"Branch %s does not exist at %s. %s",
|
|
153
|
+
branch,
|
|
154
|
+
next(iter(self.repo.remotes.origin.urls)),
|
|
155
|
+
git_error,
|
|
156
|
+
)
|
|
157
|
+
raise BranchDoesNotExist(
|
|
158
|
+
f"Please create branch '{branch}' in upstream and try again."
|
|
159
|
+
f" If this is a new repo, please add a commit before syncing. {git_error}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
logger.info("Checking out latest commit on branch %s...", branch)
|
|
163
|
+
branch_head.checkout()
|
|
164
|
+
# No specific commit hash was given, so make sure we get the latest from origin
|
|
165
|
+
# We would use repo.remotes.origin.pull() here, but that will fail in the case where someone has
|
|
166
|
+
# force-pushed to the upstream repo since the last time we did a pull. To be safe, we reset instead.
|
|
167
|
+
self.repo.head.reset(f"origin/{branch}", index=True, working_tree=True)
|
|
168
|
+
logger.info("Latest commit on branch `%s` is `%s`", branch, self.head)
|
|
169
|
+
return (self.head, True)
|
|
170
|
+
|
|
171
|
+
if is_tag:
|
|
172
|
+
tag = self.repo.tags[branch]
|
|
173
|
+
if commit_hexsha:
|
|
174
|
+
# Sanity check
|
|
175
|
+
if not tag.commit.hexsha.startswith(commit_hexsha):
|
|
176
|
+
raise RuntimeError(
|
|
177
|
+
f"Requested to check out tag {branch} and commit {commit_hexsha} together, "
|
|
178
|
+
f"but tag {branch} is actually commit {tag.commit.hexsha}!"
|
|
179
|
+
)
|
|
180
|
+
logger.info("Checking out tag %s...", branch)
|
|
181
|
+
self.repo.git.checkout(branch)
|
|
182
|
+
return (self.head, True)
|
|
183
|
+
|
|
184
|
+
if maybe_commit:
|
|
185
|
+
# Sanity check
|
|
186
|
+
if commit_hexsha and not (commit_hexsha.startswith(branch) or branch.startswith(commit_hexsha)):
|
|
187
|
+
raise RuntimeError(
|
|
188
|
+
f"Requested to check out both {branch} and {commit_hexsha} together, "
|
|
189
|
+
f"but {branch} is neither a branch, a tag, nor the same commit hash!"
|
|
130
190
|
)
|
|
131
191
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
#
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
logger.info("Checking out commit %s...", branch)
|
|
193
|
+
self.repo.git.checkout(branch)
|
|
194
|
+
return (self.head, True)
|
|
195
|
+
|
|
196
|
+
# Fallthru
|
|
197
|
+
raise BranchDoesNotExist(
|
|
198
|
+
f"{branch} does not appear to be an existing branch, tag, or possible commit hash. "
|
|
199
|
+
"Please check your upstream repository and the data you are using."
|
|
200
|
+
)
|
|
141
201
|
|
|
142
202
|
def diff_remote(self, branch):
|
|
143
203
|
logger.debug("Fetching from remote.")
|
|
144
204
|
self.fetch()
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
# Is `branch` actually a branch, a tag, or a commit? Heuristics:
|
|
206
|
+
is_branch = branch in self.repo.remotes.origin.refs
|
|
207
|
+
is_tag = branch in self.repo.tags
|
|
208
|
+
maybe_commit = set(branch).issubset(string.hexdigits)
|
|
209
|
+
logger.debug(
|
|
210
|
+
"Branch %s --> is_branch: %s, is_tag: %s, maybe_commit: %s",
|
|
211
|
+
branch,
|
|
212
|
+
is_branch,
|
|
213
|
+
is_tag,
|
|
214
|
+
maybe_commit,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if not is_branch and not is_tag and not maybe_commit:
|
|
218
|
+
logger.error("Branch %s does not exist at %s", branch, next(iter(self.repo.remotes.origin.urls)))
|
|
152
219
|
raise BranchDoesNotExist(
|
|
153
220
|
f"Please create branch '{branch}' in upstream and try again."
|
|
154
|
-
f" If this is a new repo, please add a commit before syncing.
|
|
221
|
+
f" If this is a new repo, please add a commit before syncing."
|
|
155
222
|
)
|
|
156
223
|
|
|
157
|
-
|
|
158
|
-
|
|
224
|
+
if is_branch:
|
|
225
|
+
logger.debug("Getting diff between local branch and remote branch")
|
|
226
|
+
diff = self.repo.git.diff("--name-status", f"origin/{branch}")
|
|
227
|
+
else:
|
|
228
|
+
logger.debug("Getting diff between local state and specified tag or commit")
|
|
229
|
+
diff = self.repo.git.diff("--name-status", branch)
|
|
230
|
+
|
|
159
231
|
if diff: # if diff is not empty
|
|
160
232
|
return convert_git_diff_log_to_list(diff)
|
|
161
233
|
logger.debug("No Difference")
|
|
@@ -24,6 +24,15 @@ def _temporarily_add_to_sys_path(path):
|
|
|
24
24
|
sys.path = old_sys_path
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def clear_module_from_sys_modules(module_name):
|
|
28
|
+
"""
|
|
29
|
+
Remove the module and all its submodules from sys.modules.
|
|
30
|
+
"""
|
|
31
|
+
for name in list(sys.modules.keys()):
|
|
32
|
+
if name == module_name or name.startswith(f"{module_name}."):
|
|
33
|
+
del sys.modules[name]
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
28
37
|
"""
|
|
29
38
|
Import modules from the filesystem without adding the path permanently to `sys.path`.
|
|
@@ -69,7 +78,7 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
|
69
78
|
continue
|
|
70
79
|
|
|
71
80
|
if discovered_module_name in sys.modules:
|
|
72
|
-
|
|
81
|
+
clear_module_from_sys_modules(discovered_module_name)
|
|
73
82
|
|
|
74
83
|
try:
|
|
75
84
|
if not is_package:
|
|
@@ -81,7 +90,6 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
|
81
90
|
spec.loader.exec_module(module)
|
|
82
91
|
else:
|
|
83
92
|
module = importlib.import_module(discovered_module_name)
|
|
84
|
-
|
|
85
93
|
importlib.reload(module)
|
|
86
94
|
except Exception as exc:
|
|
87
95
|
logger.error("Unable to load module %s from %s: %s", discovered_module_name, path, exc)
|
nautobot/core/views/utils.py
CHANGED
|
@@ -215,11 +215,28 @@ def handle_protectederror(obj_list, request, e):
|
|
|
215
215
|
protected_count,
|
|
216
216
|
)
|
|
217
217
|
|
|
218
|
+
# Format objects based on whether they have a detail view/absolute url
|
|
219
|
+
objects_with_absolute_url = []
|
|
220
|
+
objects_without_absolute_url = []
|
|
218
221
|
# Append dependent objects to error message
|
|
222
|
+
for dependent in protected_objects[:50]:
|
|
223
|
+
try:
|
|
224
|
+
dependent.get_absolute_url()
|
|
225
|
+
objects_with_absolute_url.append(dependent)
|
|
226
|
+
except AttributeError:
|
|
227
|
+
objects_without_absolute_url.append(dependent)
|
|
228
|
+
|
|
219
229
|
err_message += format_html_join(
|
|
220
230
|
", ",
|
|
221
231
|
'<a href="{}">{}</a>',
|
|
222
|
-
((dependent.get_absolute_url(), dependent) for dependent in
|
|
232
|
+
((dependent.get_absolute_url(), dependent) for dependent in objects_with_absolute_url),
|
|
233
|
+
)
|
|
234
|
+
if objects_with_absolute_url and objects_without_absolute_url:
|
|
235
|
+
err_message += format_html(", ")
|
|
236
|
+
err_message += format_html_join(
|
|
237
|
+
", ",
|
|
238
|
+
"<span>{}</span>",
|
|
239
|
+
((dependent,) for dependent in objects_without_absolute_url),
|
|
223
240
|
)
|
|
224
241
|
|
|
225
242
|
messages.error(request, err_message)
|
nautobot/dcim/factory.py
CHANGED
|
@@ -708,6 +708,6 @@ class ControllerManagedDeviceGroupFactory(PrimaryModelFactory):
|
|
|
708
708
|
name = UniqueFaker("word")
|
|
709
709
|
parent = factory.Maybe("has_parent", random_instance(ControllerManagedDeviceGroup), None)
|
|
710
710
|
controller = factory.LazyAttribute(
|
|
711
|
-
lambda o: o.parent.controller if o.parent else Controller.objects.
|
|
711
|
+
lambda o: o.parent.controller if o.parent else factory.random.randgen.choice(Controller.objects.all())
|
|
712
712
|
)
|
|
713
713
|
weight = factory.Faker("pyint", min_value=1, max_value=1000)
|
|
@@ -102,13 +102,13 @@ __all__ = (
|
|
|
102
102
|
"ControllerManagedDeviceGroupFilterSet",
|
|
103
103
|
"DeviceBayFilterSet",
|
|
104
104
|
"DeviceBayTemplateFilterSet",
|
|
105
|
+
"DeviceFamilyFilterSet",
|
|
105
106
|
"DeviceFilterSet",
|
|
106
107
|
"DeviceRedundancyGroupFilterSet",
|
|
107
108
|
"DeviceTypeFilterSet",
|
|
108
109
|
"DeviceTypeToSoftwareImageFileFilterSet",
|
|
109
110
|
"FrontPortFilterSet",
|
|
110
111
|
"FrontPortTemplateFilterSet",
|
|
111
|
-
"DeviceFamilyFilterSet",
|
|
112
112
|
"InterfaceConnectionFilterSet",
|
|
113
113
|
"InterfaceFilterSet",
|
|
114
114
|
"InterfaceRedundancyGroupFilterSet",
|
nautobot/dcim/forms.py
CHANGED
|
@@ -728,6 +728,15 @@ class ManufacturerForm(NautobotModelForm):
|
|
|
728
728
|
]
|
|
729
729
|
|
|
730
730
|
|
|
731
|
+
class ManufacturerFilterForm(NautobotFilterForm):
|
|
732
|
+
model = Manufacturer
|
|
733
|
+
q = forms.CharField(required=False, label="Search")
|
|
734
|
+
device_types = DynamicModelMultipleChoiceField(
|
|
735
|
+
queryset=DeviceType.objects.all(), to_field_name="model", required=False
|
|
736
|
+
)
|
|
737
|
+
platforms = DynamicModelMultipleChoiceField(queryset=Platform.objects.all(), to_field_name="name", required=False)
|
|
738
|
+
|
|
739
|
+
|
|
731
740
|
#
|
|
732
741
|
# Device Family
|
|
733
742
|
#
|
|
@@ -745,6 +754,9 @@ class DeviceFamilyForm(NautobotModelForm):
|
|
|
745
754
|
class DeviceFamilyFilterForm(NautobotFilterForm):
|
|
746
755
|
model = DeviceFamily
|
|
747
756
|
q = forms.CharField(required=False, label="Search")
|
|
757
|
+
device_types = DynamicModelMultipleChoiceField(
|
|
758
|
+
queryset=DeviceType.objects.all(), to_field_name="model", required=False
|
|
759
|
+
)
|
|
748
760
|
tags = TagFilterField(model)
|
|
749
761
|
|
|
750
762
|
|
|
@@ -1582,6 +1594,13 @@ class PlatformForm(NautobotModelForm):
|
|
|
1582
1594
|
}
|
|
1583
1595
|
|
|
1584
1596
|
|
|
1597
|
+
class PlatformFilterForm(NautobotFilterForm):
|
|
1598
|
+
model = Platform
|
|
1599
|
+
q = forms.CharField(required=False, label="Search")
|
|
1600
|
+
name = forms.CharField(required=False)
|
|
1601
|
+
network_driver = forms.CharField(required=False)
|
|
1602
|
+
|
|
1603
|
+
|
|
1585
1604
|
#
|
|
1586
1605
|
# Devices
|
|
1587
1606
|
#
|
|
@@ -2567,14 +2586,14 @@ class InterfaceBulkEditForm(
|
|
|
2567
2586
|
queryset=VLAN.objects.all(),
|
|
2568
2587
|
required=False,
|
|
2569
2588
|
query_params={
|
|
2570
|
-
"
|
|
2589
|
+
"locations": "null",
|
|
2571
2590
|
},
|
|
2572
2591
|
)
|
|
2573
2592
|
tagged_vlans = DynamicModelMultipleChoiceField(
|
|
2574
2593
|
queryset=VLAN.objects.all(),
|
|
2575
2594
|
required=False,
|
|
2576
2595
|
query_params={
|
|
2577
|
-
"
|
|
2596
|
+
"locations": "null",
|
|
2578
2597
|
},
|
|
2579
2598
|
)
|
|
2580
2599
|
vrf = DynamicModelChoiceField(
|
|
@@ -2618,8 +2637,8 @@ class InterfaceBulkEditForm(
|
|
|
2618
2637
|
# Limit VLAN choices by Location
|
|
2619
2638
|
if locations.count() == 1:
|
|
2620
2639
|
location = locations.first()
|
|
2621
|
-
self.fields["untagged_vlan"].widget.add_query_param("
|
|
2622
|
-
self.fields["tagged_vlans"].widget.add_query_param("
|
|
2640
|
+
self.fields["untagged_vlan"].widget.add_query_param("locations", location.pk)
|
|
2641
|
+
self.fields["tagged_vlans"].widget.add_query_param("locations", location.pk)
|
|
2623
2642
|
|
|
2624
2643
|
# Restrict parent/bridge/LAG interface assignment by device (or VC master)
|
|
2625
2644
|
if device_count == 1:
|
|
@@ -45,9 +45,15 @@ __all__ = (
|
|
|
45
45
|
class ManufacturerTable(BaseTable):
|
|
46
46
|
pk = ToggleColumn()
|
|
47
47
|
name = tables.LinkColumn()
|
|
48
|
-
device_type_count =
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
device_type_count = LinkedCountColumn(
|
|
49
|
+
viewname="dcim:devicetype_list", url_params={"manufacturer": "name"}, verbose_name="Device Types"
|
|
50
|
+
)
|
|
51
|
+
inventory_item_count = LinkedCountColumn(
|
|
52
|
+
viewname="dcim:inventoryitem_list", url_params={"manufacturer": "name"}, verbose_name="Inventory Items"
|
|
53
|
+
)
|
|
54
|
+
platform_count = LinkedCountColumn(
|
|
55
|
+
viewname="dcim:platform_list", url_params={"manufacturer": "name"}, verbose_name="Platforms"
|
|
56
|
+
)
|
|
51
57
|
actions = ButtonsColumn(Manufacturer)
|
|
52
58
|
|
|
53
59
|
class Meta(BaseTable.Meta):
|
|
@@ -71,7 +77,9 @@ class ManufacturerTable(BaseTable):
|
|
|
71
77
|
class DeviceFamilyTable(BaseTable):
|
|
72
78
|
pk = ToggleColumn()
|
|
73
79
|
name = tables.Column(linkify=True)
|
|
74
|
-
device_type_count =
|
|
80
|
+
device_type_count = LinkedCountColumn(
|
|
81
|
+
viewname="dcim:devicetype_list", url_params={"device_family": "name"}, verbose_name="Device Types"
|
|
82
|
+
)
|
|
75
83
|
actions = ButtonsColumn(DeviceFamily)
|
|
76
84
|
tags = TagColumn(url_name="dcim:devicefamily_list")
|
|
77
85
|
|
|
@@ -95,6 +103,8 @@ class DeviceFamilyTable(BaseTable):
|
|
|
95
103
|
class DeviceTypeTable(BaseTable):
|
|
96
104
|
pk = ToggleColumn()
|
|
97
105
|
model = tables.Column(linkify=True, verbose_name="Device Type")
|
|
106
|
+
manufacturer = tables.Column(linkify=True)
|
|
107
|
+
device_family = tables.Column(linkify=True)
|
|
98
108
|
is_full_depth = BooleanColumn(verbose_name="Full Depth")
|
|
99
109
|
device_count = LinkedCountColumn(
|
|
100
110
|
viewname="dcim:device_list",
|
|
@@ -109,6 +119,7 @@ class DeviceTypeTable(BaseTable):
|
|
|
109
119
|
"pk",
|
|
110
120
|
"model",
|
|
111
121
|
"manufacturer",
|
|
122
|
+
"device_family",
|
|
112
123
|
"part_number",
|
|
113
124
|
"u_height",
|
|
114
125
|
"is_full_depth",
|
|
@@ -1854,12 +1854,14 @@ class SoftwareVersionTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1854
1854
|
|
|
1855
1855
|
# Only return the device types with a direct m2m relationship to the version's software image files
|
|
1856
1856
|
device_type = DeviceType.objects.filter(software_image_files__isnull=False).first()
|
|
1857
|
+
self.assertIsNotNone(device_type)
|
|
1857
1858
|
self.assertQuerysetEqualAndNotEmpty(
|
|
1858
1859
|
qs.get_for_object(device_type), qs.filter(software_image_files__device_types=device_type)
|
|
1859
1860
|
)
|
|
1860
1861
|
|
|
1861
1862
|
# Only return the software version set on the device's software_version foreign key
|
|
1862
1863
|
device = Device.objects.filter(software_version__isnull=False).first()
|
|
1864
|
+
self.assertIsNotNone(device)
|
|
1863
1865
|
self.assertQuerysetEqualAndNotEmpty(qs.get_for_object(device), [device.software_version])
|
|
1864
1866
|
|
|
1865
1867
|
# Only return the software version set on the inventory item's software_version foreign key
|
|
@@ -3398,6 +3398,48 @@ class SoftwareImageFileTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3398
3398
|
"download_url": "https://example.com/software_image_file_test_case.bin",
|
|
3399
3399
|
}
|
|
3400
3400
|
|
|
3401
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3402
|
+
def test_correct_handling_for_model_protected_error(self):
|
|
3403
|
+
platform = Platform.objects.first()
|
|
3404
|
+
software_version_status = Status.objects.get_for_model(SoftwareVersion).first()
|
|
3405
|
+
software_image_file_status = Status.objects.get_for_model(SoftwareImageFile).first()
|
|
3406
|
+
software_version = SoftwareVersion.objects.create(
|
|
3407
|
+
platform=platform, version="Test version 1.0.0", status=software_version_status
|
|
3408
|
+
)
|
|
3409
|
+
software_image_file = SoftwareImageFile.objects.create(
|
|
3410
|
+
software_version=software_version,
|
|
3411
|
+
image_file_name="software_image_file_qs_test_1.bin",
|
|
3412
|
+
status=software_image_file_status,
|
|
3413
|
+
)
|
|
3414
|
+
device_type = DeviceType.objects.first()
|
|
3415
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
3416
|
+
device_status = Status.objects.get_for_model(Device).first()
|
|
3417
|
+
location = Location.objects.filter(location_type__name="Campus").first()
|
|
3418
|
+
Device.objects.create(
|
|
3419
|
+
device_type=device_type,
|
|
3420
|
+
role=device_role,
|
|
3421
|
+
name="Device 1",
|
|
3422
|
+
location=location,
|
|
3423
|
+
status=device_status,
|
|
3424
|
+
software_version=software_version,
|
|
3425
|
+
)
|
|
3426
|
+
device_type_to_software_image_file = DeviceTypeToSoftwareImageFile.objects.create(
|
|
3427
|
+
device_type=device_type, software_image_file=software_image_file
|
|
3428
|
+
)
|
|
3429
|
+
|
|
3430
|
+
self.add_permissions("dcim.delete_softwareimagefile")
|
|
3431
|
+
pk_list = [software_image_file.pk]
|
|
3432
|
+
data = {
|
|
3433
|
+
"pk": pk_list,
|
|
3434
|
+
"confirm": True,
|
|
3435
|
+
"_confirm": True, # Form button
|
|
3436
|
+
}
|
|
3437
|
+
response = self.client.post(self._get_url("bulk_delete"), data, follow=True)
|
|
3438
|
+
self.assertHttpStatus(response, 200)
|
|
3439
|
+
response_body = response.content.decode(response.charset)
|
|
3440
|
+
# Assert protected error message included in the response body
|
|
3441
|
+
self.assertInHTML(f"<span>{device_type_to_software_image_file}</span>", response_body)
|
|
3442
|
+
|
|
3401
3443
|
|
|
3402
3444
|
class SoftwareVersionTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
3403
3445
|
model = SoftwareVersion
|
|
@@ -3436,6 +3478,48 @@ class SoftwareVersionTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
3436
3478
|
"pre_release": True,
|
|
3437
3479
|
}
|
|
3438
3480
|
|
|
3481
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
3482
|
+
def test_correct_handling_for_model_protected_error(self):
|
|
3483
|
+
platform = Platform.objects.first()
|
|
3484
|
+
software_version_status = Status.objects.get_for_model(SoftwareVersion).first()
|
|
3485
|
+
software_image_file_status = Status.objects.get_for_model(SoftwareImageFile).first()
|
|
3486
|
+
software_version = SoftwareVersion.objects.create(
|
|
3487
|
+
platform=platform, version="Test version 1.0.0", status=software_version_status
|
|
3488
|
+
)
|
|
3489
|
+
software_image_file = SoftwareImageFile.objects.create(
|
|
3490
|
+
software_version=software_version,
|
|
3491
|
+
image_file_name="software_image_file_qs_test_1.bin",
|
|
3492
|
+
status=software_image_file_status,
|
|
3493
|
+
)
|
|
3494
|
+
device_type = DeviceType.objects.first()
|
|
3495
|
+
device_role = Role.objects.get_for_model(Device).first()
|
|
3496
|
+
device_status = Status.objects.get_for_model(Device).first()
|
|
3497
|
+
location = Location.objects.filter(location_type__name="Campus").first()
|
|
3498
|
+
Device.objects.create(
|
|
3499
|
+
device_type=device_type,
|
|
3500
|
+
role=device_role,
|
|
3501
|
+
name="Device 1",
|
|
3502
|
+
location=location,
|
|
3503
|
+
status=device_status,
|
|
3504
|
+
software_version=software_version,
|
|
3505
|
+
)
|
|
3506
|
+
device_type_to_software_image_file = DeviceTypeToSoftwareImageFile.objects.create(
|
|
3507
|
+
device_type=device_type, software_image_file=software_image_file
|
|
3508
|
+
)
|
|
3509
|
+
|
|
3510
|
+
self.add_permissions("dcim.delete_softwareversion")
|
|
3511
|
+
pk_list = [software_version.pk]
|
|
3512
|
+
data = {
|
|
3513
|
+
"pk": pk_list,
|
|
3514
|
+
"confirm": True,
|
|
3515
|
+
"_confirm": True, # Form button
|
|
3516
|
+
}
|
|
3517
|
+
response = self.client.post(self._get_url("bulk_delete"), data, follow=True)
|
|
3518
|
+
self.assertHttpStatus(response, 200)
|
|
3519
|
+
response_body = response.content.decode(response.charset)
|
|
3520
|
+
# Assert protected error message included in the response body
|
|
3521
|
+
self.assertInHTML(f"<span>{device_type_to_software_image_file}</span>", response_body)
|
|
3522
|
+
|
|
3439
3523
|
|
|
3440
3524
|
class ControllerTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
3441
3525
|
model = Controller
|
nautobot/dcim/views.py
CHANGED
|
@@ -710,6 +710,7 @@ class ManufacturerListView(generic.ObjectListView):
|
|
|
710
710
|
platform_count=count_related(Platform, "manufacturer"),
|
|
711
711
|
)
|
|
712
712
|
filterset = filters.ManufacturerFilterSet
|
|
713
|
+
filterset_form = forms.ManufacturerFilterForm
|
|
713
714
|
table = tables.ManufacturerTable
|
|
714
715
|
|
|
715
716
|
|
|
@@ -1212,6 +1213,7 @@ class PlatformListView(generic.ObjectListView):
|
|
|
1212
1213
|
virtual_machine_count=count_related(VirtualMachine, "platform"),
|
|
1213
1214
|
)
|
|
1214
1215
|
filterset = filters.PlatformFilterSet
|
|
1216
|
+
filterset_form = forms.PlatformFilterForm
|
|
1215
1217
|
table = tables.PlatformTable
|
|
1216
1218
|
|
|
1217
1219
|
|
|
@@ -3103,6 +3105,7 @@ class InterfaceRedundancyGroupAssociationUIViewSet(ObjectEditViewMixin, ObjectDe
|
|
|
3103
3105
|
|
|
3104
3106
|
class DeviceFamilyUIViewSet(NautobotUIViewSet):
|
|
3105
3107
|
filterset_class = filters.DeviceFamilyFilterSet
|
|
3108
|
+
filterset_form_class = forms.DeviceFamilyFilterForm
|
|
3106
3109
|
form_class = forms.DeviceFamilyForm
|
|
3107
3110
|
bulk_update_form_class = forms.DeviceFamilyBulkEditForm
|
|
3108
3111
|
queryset = DeviceFamily.objects.annotate(device_type_count=count_related(DeviceType, "device_family"))
|