nautobot 2.3.7__py3-none-any.whl → 2.3.9__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/tables.py +2 -0
- nautobot/core/forms/__init__.py +4 -0
- nautobot/core/forms/fields.py +32 -0
- nautobot/core/jobs/__init__.py +24 -8
- nautobot/core/models/tree_queries.py +8 -0
- nautobot/core/settings.py +7 -0
- nautobot/core/settings.yaml +10 -0
- nautobot/core/signals.py +5 -4
- nautobot/core/templates/inc/paginator.html +3 -0
- nautobot/core/templates/nautobot_config.py.j2 +4 -0
- nautobot/core/templates/utilities/obj_table.html +1 -1
- nautobot/dcim/api/serializers.py +10 -5
- nautobot/dcim/forms.py +41 -34
- nautobot/dcim/models/device_components.py +12 -4
- nautobot/dcim/tables/devices.py +4 -2
- nautobot/dcim/tests/test_api.py +28 -0
- nautobot/dcim/tests/test_forms.py +17 -1
- nautobot/dcim/tests/test_models.py +58 -4
- nautobot/dcim/utils.py +9 -6
- nautobot/extras/context_managers.py +7 -8
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +30 -49
- nautobot/extras/datasources/registry.py +2 -2
- nautobot/extras/jobs.py +17 -5
- nautobot/extras/models/datasources.py +6 -0
- nautobot/extras/models/groups.py +47 -33
- nautobot/extras/models/jobs.py +1 -1
- nautobot/extras/plugins/__init__.py +165 -0
- nautobot/extras/plugins/views.py +18 -3
- nautobot/extras/templates/extras/plugin_detail.html +33 -0
- nautobot/extras/tests/test_context_managers.py +16 -7
- nautobot/extras/tests/test_datasources.py +88 -1
- nautobot/extras/tests/test_dynamicgroups.py +12 -0
- nautobot/extras/tests/test_plugins.py +94 -0
- nautobot/extras/views.py +3 -1
- nautobot/ipam/filters.py +2 -2
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/templates/ipam/ipaddress.html +2 -2
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +3 -0
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +3 -0
- nautobot/ipam/templates/ipam/prefix.html +3 -3
- nautobot/ipam/templates/ipam/routetarget.html +2 -2
- nautobot/ipam/templates/ipam/vlan.html +3 -0
- nautobot/ipam/templates/ipam/vrf.html +7 -4
- nautobot/ipam/tests/test_models.py +68 -12
- nautobot/ipam/views.py +43 -0
- nautobot/project-static/docs/404.html +24 -3
- nautobot/project-static/docs/apps/index.html +24 -3
- nautobot/project-static/docs/apps/nautobot-apps.html +24 -3
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js → bundle.83f73b43.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js.map → bundle.83f73b43.min.js.map} +3 -3
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +49 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +138 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +24 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +24 -3
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/global-search.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +27 -6
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +8823 -0
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +27 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +24 -3
- nautobot/project-static/docs/development/apps/api/setup.html +33 -11
- nautobot/project-static/docs/development/apps/api/testing.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/notes.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/urls.html +24 -3
- nautobot/project-static/docs/development/apps/index.html +24 -3
- nautobot/project-static/docs/development/apps/migration/code-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +24 -3
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +24 -3
- nautobot/project-static/docs/development/core/application-registry.html +24 -3
- nautobot/project-static/docs/development/core/best-practices.html +24 -3
- nautobot/project-static/docs/development/core/bootstrap-ui.html +24 -3
- nautobot/project-static/docs/development/core/caching.html +24 -3
- nautobot/project-static/docs/development/core/controllers.html +24 -3
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +24 -3
- nautobot/project-static/docs/development/core/generic-views.html +24 -3
- nautobot/project-static/docs/development/core/getting-started.html +24 -3
- nautobot/project-static/docs/development/core/homepage.html +24 -3
- nautobot/project-static/docs/development/core/index.html +24 -3
- nautobot/project-static/docs/development/core/model-checklist.html +24 -3
- nautobot/project-static/docs/development/core/model-features.html +24 -3
- nautobot/project-static/docs/development/core/natural-keys.html +24 -3
- nautobot/project-static/docs/development/core/navigation-menu.html +24 -3
- nautobot/project-static/docs/development/core/release-checklist.html +24 -3
- nautobot/project-static/docs/development/core/role-internals.html +24 -3
- nautobot/project-static/docs/development/core/settings.html +24 -3
- nautobot/project-static/docs/development/core/style-guide.html +24 -3
- nautobot/project-static/docs/development/core/templates.html +24 -3
- nautobot/project-static/docs/development/core/testing.html +24 -3
- nautobot/project-static/docs/development/core/user-preferences.html +24 -3
- nautobot/project-static/docs/development/index.html +24 -3
- nautobot/project-static/docs/development/jobs/index.html +24 -3
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +24 -3
- nautobot/project-static/docs/index.html +24 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +24 -3
- nautobot/project-static/docs/overview/design_philosophy.html +24 -3
- nautobot/project-static/docs/release-notes/index.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.3.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.4.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.6.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.3.html +337 -109
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +273 -269
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -4
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/services.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +24 -3
- nautobot/project-static/docs/user-guide/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +24 -3
- nautobot/project-static/js/forms.js +41 -5
- nautobot/virtualization/tables.py +1 -1
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/METADATA +2 -2
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/RECORD +334 -333
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +0 -1
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/NOTICE +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/WHEEL +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/entry_points.txt +0 -0
nautobot/apps/tables.py
CHANGED
|
@@ -15,6 +15,7 @@ from nautobot.core.tables import (
|
|
|
15
15
|
TagColumn,
|
|
16
16
|
ToggleColumn,
|
|
17
17
|
)
|
|
18
|
+
from nautobot.extras.plugins import TableExtension
|
|
18
19
|
from nautobot.extras.tables import RoleTableMixin, StatusTableMixin
|
|
19
20
|
|
|
20
21
|
__all__ = (
|
|
@@ -32,5 +33,6 @@ __all__ = (
|
|
|
32
33
|
"RoleTableMixin",
|
|
33
34
|
"StatusTableMixin",
|
|
34
35
|
"TagColumn",
|
|
36
|
+
"TableExtension",
|
|
35
37
|
"ToggleColumn",
|
|
36
38
|
)
|
nautobot/core/forms/__init__.py
CHANGED
|
@@ -7,6 +7,8 @@ from nautobot.core.forms.constants import (
|
|
|
7
7
|
NUMERIC_EXPANSION_PATTERN,
|
|
8
8
|
)
|
|
9
9
|
from nautobot.core.forms.fields import (
|
|
10
|
+
AutoPositionField,
|
|
11
|
+
AutoPositionPatternField,
|
|
10
12
|
CommentField,
|
|
11
13
|
CSVChoiceField,
|
|
12
14
|
CSVContentTypeField,
|
|
@@ -80,6 +82,8 @@ __all__ = (
|
|
|
80
82
|
"ALPHANUMERIC_EXPANSION_PATTERN",
|
|
81
83
|
"APISelect",
|
|
82
84
|
"APISelectMultiple",
|
|
85
|
+
"AutoPositionField",
|
|
86
|
+
"AutoPositionPatternField",
|
|
83
87
|
"BOOLEAN_CHOICES",
|
|
84
88
|
"BOOLEAN_WITH_BLANK_CHOICES",
|
|
85
89
|
"BootstrapMixin",
|
nautobot/core/forms/fields.py
CHANGED
|
@@ -444,6 +444,38 @@ class SlugField(django_forms.SlugField):
|
|
|
444
444
|
self.widget.attrs["slug-source"] = slug_source
|
|
445
445
|
|
|
446
446
|
|
|
447
|
+
class AutoPositionField(django_forms.CharField):
|
|
448
|
+
def __init__(self, source="name", *args, **kwargs):
|
|
449
|
+
"""
|
|
450
|
+
Instantiate a AutoPositionField.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
source (str, tuple): Name of the field (or a list of field names) that will be used to suggest a position.
|
|
454
|
+
"""
|
|
455
|
+
kwargs.setdefault("label", "Position")
|
|
456
|
+
kwargs.setdefault("widget", forms.SlugWidget)
|
|
457
|
+
super().__init__(*args, **kwargs)
|
|
458
|
+
if isinstance(source, (tuple, list)):
|
|
459
|
+
source = " ".join(source)
|
|
460
|
+
self.widget.attrs["source"] = source
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class AutoPositionPatternField(ExpandableNameField):
|
|
464
|
+
def __init__(self, source="name_pattern", *args, **kwargs):
|
|
465
|
+
"""
|
|
466
|
+
Instantiate a AutoPositionPatternField.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
source (str, tuple): Name pattern of the field (or a list of field names) that will be used to suggest a position pattern.
|
|
470
|
+
"""
|
|
471
|
+
kwargs.setdefault("label", "Position")
|
|
472
|
+
kwargs.setdefault("widget", forms.SlugWidget)
|
|
473
|
+
super().__init__(*args, **kwargs)
|
|
474
|
+
if isinstance(source, (tuple, list)):
|
|
475
|
+
source = " ".join(source)
|
|
476
|
+
self.widget.attrs["source"] = source
|
|
477
|
+
|
|
478
|
+
|
|
447
479
|
class DynamicModelChoiceMixin:
|
|
448
480
|
"""
|
|
449
481
|
:param display_field: The name of the attribute of an API response object to display in the selection list
|
nautobot/core/jobs/__init__.py
CHANGED
|
@@ -19,7 +19,12 @@ from nautobot.core.jobs.cleanup import LogsCleanup
|
|
|
19
19
|
from nautobot.core.jobs.groups import RefreshDynamicGroupCaches
|
|
20
20
|
from nautobot.core.utils.lookup import get_filterset_for_model
|
|
21
21
|
from nautobot.core.utils.requests import get_filterable_params_from_filter_params
|
|
22
|
-
from nautobot.extras.datasources import
|
|
22
|
+
from nautobot.extras.datasources import (
|
|
23
|
+
ensure_git_repository,
|
|
24
|
+
git_repository_dry_run,
|
|
25
|
+
refresh_datasource_content,
|
|
26
|
+
refresh_job_code_from_repository,
|
|
27
|
+
)
|
|
23
28
|
from nautobot.extras.jobs import BooleanVar, ChoiceVar, FileVar, Job, ObjectVar, RunJobTaskFailed, StringVar, TextVar
|
|
24
29
|
from nautobot.extras.models import ExportTemplate, GitRepository
|
|
25
30
|
|
|
@@ -49,13 +54,24 @@ class GitRepositorySync(Job):
|
|
|
49
54
|
self.logger.info(f'Creating/refreshing local copy of Git repository "{repository.name}"...')
|
|
50
55
|
|
|
51
56
|
try:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
with transaction.atomic():
|
|
58
|
+
ensure_git_repository(repository, logger=self.logger)
|
|
59
|
+
refresh_datasource_content("extras.gitrepository", repository, user, job_result, delete=False)
|
|
60
|
+
# Given that the above succeeded, tell all workers (including ourself) to call ensure_git_repository()
|
|
61
|
+
app.control.broadcast(
|
|
62
|
+
"refresh_git_repository", repository_pk=repository.pk, head=repository.current_head
|
|
63
|
+
)
|
|
64
|
+
if job_result.duration:
|
|
65
|
+
self.logger.info("Repository synchronization completed in %s", job_result.duration)
|
|
66
|
+
except Exception:
|
|
67
|
+
job_result.log("Changes to database records have been reverted.")
|
|
68
|
+
# Re-check-out previous commit if any
|
|
69
|
+
repository.refresh_from_db()
|
|
70
|
+
if repository.current_head:
|
|
71
|
+
job_result.log(f"Attempting to revert local repository clone to commit {repository.current_head}")
|
|
72
|
+
ensure_git_repository(repository, logger=self.logger, head=repository.current_head)
|
|
73
|
+
refresh_job_code_from_repository(repository.slug, ignore_import_errors=True)
|
|
74
|
+
raise
|
|
59
75
|
|
|
60
76
|
|
|
61
77
|
class GitRepositoryDryRun(Job):
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from django.core.cache import cache
|
|
2
2
|
from django.db.models import Case, When
|
|
3
|
+
from django.db.models.signals import post_delete, post_save
|
|
3
4
|
from tree_queries.models import TreeNode
|
|
4
5
|
from tree_queries.query import TreeManager as TreeManager_, TreeQuerySet as TreeQuerySet_
|
|
5
6
|
|
|
6
7
|
from nautobot.core.models import BaseManager, querysets
|
|
8
|
+
from nautobot.core.signals import invalidate_max_depth_cache
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class TreeQuerySet(TreeQuerySet_, querysets.RestrictedQuerySet):
|
|
@@ -117,3 +119,9 @@ class TreeModel(TreeNode):
|
|
|
117
119
|
display_str += self.name
|
|
118
120
|
cache.set(cache_key, display_str, 5)
|
|
119
121
|
return display_str
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def __init_subclass__(cls, **kwargs):
|
|
125
|
+
super().__init_subclass__(**kwargs)
|
|
126
|
+
post_save.connect(invalidate_max_depth_cache, sender=cls)
|
|
127
|
+
post_delete.connect(invalidate_max_depth_cache, sender=cls)
|
nautobot/core/settings.py
CHANGED
|
@@ -418,7 +418,14 @@ if "NAUTOBOT_ALLOWED_HOSTS" in os.environ and os.environ["NAUTOBOT_ALLOWED_HOSTS
|
|
|
418
418
|
ALLOWED_HOSTS = os.environ["NAUTOBOT_ALLOWED_HOSTS"].split(" ")
|
|
419
419
|
else:
|
|
420
420
|
ALLOWED_HOSTS = []
|
|
421
|
+
|
|
422
|
+
# Allow CSRF trusted origins to be set via an environment variable
|
|
423
|
+
# This allows Nautobot to be run behind a reverse proxy that terminates TLS
|
|
424
|
+
# and fix potential issues with CSRF validation
|
|
421
425
|
CSRF_TRUSTED_ORIGINS = []
|
|
426
|
+
if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
|
|
427
|
+
CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
|
|
428
|
+
|
|
422
429
|
CSRF_FAILURE_VIEW = "nautobot.core.views.csrf_failure"
|
|
423
430
|
DATE_FORMAT = os.getenv("NAUTOBOT_DATE_FORMAT", "N j, Y")
|
|
424
431
|
DATETIME_FORMAT = os.getenv("NAUTOBOT_DATETIME_FORMAT", "N j, Y g:i a")
|
nautobot/core/settings.yaml
CHANGED
|
@@ -587,6 +587,16 @@ properties:
|
|
|
587
587
|
description: >-
|
|
588
588
|
A list of hosts (fully-qualified domain names (FQDNs) or subdomains) that are considered trusted origins
|
|
589
589
|
for cross-site secure requests such as HTTPS POST.
|
|
590
|
+
|
|
591
|
+
You might need to set this if you are using a reverse proxy or load balancer that changes the host header or if you face the error
|
|
592
|
+
'Invalid Form Submission - CSRF failure has occured' and 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
|
|
593
|
+
|
|
594
|
+
Example:
|
|
595
|
+
|
|
596
|
+
```python
|
|
597
|
+
CSRF_TRUSTED_ORIGINS = ['https://subdomain.example.com', 'https://*.example.com']
|
|
598
|
+
```
|
|
599
|
+
environment_variable: "NAUTOBOT_CSRF_TRUSTED_ORIGINS"
|
|
590
600
|
items:
|
|
591
601
|
type: "string"
|
|
592
602
|
see_also:
|
nautobot/core/signals.py
CHANGED
|
@@ -7,7 +7,6 @@ import logging
|
|
|
7
7
|
|
|
8
8
|
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
|
9
9
|
from django.core.cache import cache
|
|
10
|
-
from django.db.models.signals import post_delete, post_save
|
|
11
10
|
from django.dispatch import receiver, Signal
|
|
12
11
|
import redis.exceptions
|
|
13
12
|
|
|
@@ -62,10 +61,12 @@ def disable_for_loaddata(signal_handler):
|
|
|
62
61
|
return wrapper
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
@receiver(post_save)
|
|
66
|
-
@receiver(post_delete)
|
|
67
64
|
def invalidate_max_depth_cache(sender, **kwargs):
|
|
68
|
-
"""
|
|
65
|
+
"""
|
|
66
|
+
Clear the appropriate TreeManager.max_depth cache as the create/update/delete may have changed the tree.
|
|
67
|
+
|
|
68
|
+
Note that this signal is connected in `TreeModel.__init_subclass__()` so as to only apply to those models.
|
|
69
|
+
"""
|
|
69
70
|
from nautobot.core.models.tree_queries import TreeManager
|
|
70
71
|
|
|
71
72
|
if not isinstance(sender.objects, TreeManager):
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
{% endif %}
|
|
30
30
|
{% endfor %}
|
|
31
31
|
<select name="per_page" id="per_page" class="form-control">
|
|
32
|
+
{% if page.paginator.per_page not in "PER_PAGE_DEFAULTS"|settings_or_config %}
|
|
33
|
+
<option value="{{ page.paginator.per_page }}" selected="selected">{{ page.paginator.per_page }}</option>
|
|
34
|
+
{% endif %}
|
|
32
35
|
{% for n in "PER_PAGE_DEFAULTS"|settings_or_config %}
|
|
33
36
|
<option value="{{ n }}"{% if page.paginator.per_page == n %} selected="selected"{% endif %}>{{ n }}</option>
|
|
34
37
|
{% endfor %}
|
|
@@ -94,9 +94,13 @@ SECRET_KEY = os.getenv("NAUTOBOT_SECRET_KEY", "{{ secret_key }}")
|
|
|
94
94
|
# FQDNs that are considered trusted origins for secure, cross-domain, requests such as HTTPS POST.
|
|
95
95
|
# If running Nautobot under a single domain, you may not need to set this variable;
|
|
96
96
|
# if running on multiple domains, you *may* need to set this variable to more or less the same as ALLOWED_HOSTS above.
|
|
97
|
+
# You also want to set this variable if you are facing CSRF validation issues such as
|
|
98
|
+
# 'CSRF failure has occured' or 'Origin checking failed - https://subdomain.example.com does not match any trusted origins.'
|
|
97
99
|
# https://docs.djangoproject.com/en/stable/ref/settings/#csrf-trusted-origins
|
|
98
100
|
#
|
|
99
101
|
# CSRF_TRUSTED_ORIGINS = []
|
|
102
|
+
# if "NAUTOBOT_CSRF_TRUSTED_ORIGINS" in os.environ and os.environ["NAUTOBOT_CSRF_TRUSTED_ORIGINS"] != "":
|
|
103
|
+
# CSRF_TRUSTED_ORIGINS = os.getenv("NAUTOBOT_CSRF_TRUSTED_ORIGINS", "").split(_CONFIG_SETTING_SEPARATOR)
|
|
100
104
|
|
|
101
105
|
# Date/time formatting. See the following link for supported formats:
|
|
102
106
|
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{% else %}
|
|
46
46
|
{% include table_template|default:'responsive_table.html' %}
|
|
47
47
|
{% endif %}
|
|
48
|
-
{% if not disable_pagination %}
|
|
48
|
+
{% if table.paginator.num_pages > 1 and not disable_pagination %}
|
|
49
49
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
|
50
50
|
{% endif %}
|
|
51
51
|
<div class="clearfix"></div>
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -798,14 +798,19 @@ class InterfaceSerializer(
|
|
|
798
798
|
def validate(self, data):
|
|
799
799
|
# Validate many-to-many VLAN assignments
|
|
800
800
|
device = self.instance.device if self.instance else data.get("device")
|
|
801
|
-
|
|
802
|
-
|
|
801
|
+
location = None
|
|
802
|
+
if device:
|
|
803
|
+
location = device.location
|
|
804
|
+
if location:
|
|
805
|
+
location_ids = location.ancestors(include_self=True).values_list("id", flat=True)
|
|
806
|
+
else:
|
|
807
|
+
location_ids = []
|
|
803
808
|
for vlan in data.get("tagged_vlans", []):
|
|
804
|
-
if vlan.locations.exists() and not vlan.locations.filter(
|
|
809
|
+
if vlan.locations.exists() and not vlan.locations.filter(pk__in=location_ids).exists():
|
|
805
810
|
raise serializers.ValidationError(
|
|
806
811
|
{
|
|
807
|
-
"tagged_vlans": f"VLAN {vlan} must have
|
|
808
|
-
f"it must be global."
|
|
812
|
+
"tagged_vlans": f"VLAN {vlan} must have the same location as the interface's parent device, "
|
|
813
|
+
f"or is in one of the parents of the interface's parent device's location, or it must be global."
|
|
809
814
|
}
|
|
810
815
|
)
|
|
811
816
|
|
nautobot/dcim/forms.py
CHANGED
|
@@ -13,6 +13,8 @@ from nautobot.core.forms import (
|
|
|
13
13
|
add_blank_choice,
|
|
14
14
|
APISelect,
|
|
15
15
|
APISelectMultiple,
|
|
16
|
+
AutoPositionField,
|
|
17
|
+
AutoPositionPatternField,
|
|
16
18
|
BootstrapMixin,
|
|
17
19
|
BulkEditNullBooleanSelect,
|
|
18
20
|
ColorSelect,
|
|
@@ -213,23 +215,27 @@ class InterfaceCommonForm(forms.Form):
|
|
|
213
215
|
elif mode == InterfaceModeChoices.MODE_TAGGED_ALL:
|
|
214
216
|
self.cleaned_data["tagged_vlans"] = []
|
|
215
217
|
|
|
216
|
-
# Validate tagged VLANs; must be a global VLAN or in the same location
|
|
217
|
-
#
|
|
218
|
-
# belongs to the parent Location or the child location of the parent device to the `tagged_vlan` field of the interface?
|
|
218
|
+
# Validate tagged VLANs; must be a global VLAN or in the same location as the
|
|
219
|
+
# parent device/VM or any of that location's parent locations
|
|
219
220
|
elif mode == InterfaceModeChoices.MODE_TAGGED:
|
|
220
|
-
|
|
221
|
+
location = self.cleaned_data[parent_field].location
|
|
222
|
+
if location:
|
|
223
|
+
location_ids = location.ancestors(include_self=True).values_list("id", flat=True)
|
|
224
|
+
else:
|
|
225
|
+
location_ids = []
|
|
221
226
|
invalid_vlans = [
|
|
222
227
|
str(v)
|
|
223
228
|
for v in tagged_vlans
|
|
224
229
|
if v.locations.without_tree_fields().exists()
|
|
225
|
-
and not VLANLocationAssignment.objects.filter(
|
|
230
|
+
and not VLANLocationAssignment.objects.filter(location__in=location_ids, vlan=v).exists()
|
|
226
231
|
]
|
|
227
232
|
|
|
228
233
|
if invalid_vlans:
|
|
229
234
|
raise forms.ValidationError(
|
|
230
235
|
{
|
|
231
|
-
"tagged_vlans": f"The tagged VLANs ({', '.join(invalid_vlans)}) must
|
|
232
|
-
|
|
236
|
+
"tagged_vlans": f"The tagged VLANs ({', '.join(invalid_vlans)}) must have the same location as the "
|
|
237
|
+
"interface's parent device, or is in one of the parents of the interface's parent device's location, "
|
|
238
|
+
"or it must be global."
|
|
233
239
|
}
|
|
234
240
|
)
|
|
235
241
|
|
|
@@ -265,23 +271,7 @@ class ComponentForm(BootstrapMixin, forms.Form):
|
|
|
265
271
|
|
|
266
272
|
|
|
267
273
|
class ModularComponentForm(ComponentForm):
|
|
268
|
-
|
|
269
|
-
label="Name",
|
|
270
|
-
help_text="""
|
|
271
|
-
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
272
|
-
are not supported. Examples:
|
|
273
|
-
<ul>
|
|
274
|
-
<li><code>[ge,xe]-0/0/[0-9]</code></li>
|
|
275
|
-
<li><code>e[0-3][a-d,f]</code></li>
|
|
276
|
-
</ul>
|
|
277
|
-
|
|
278
|
-
The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
|
|
279
|
-
may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
|
|
280
|
-
module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
|
|
281
|
-
multiple times in the component name and there is no limit to the depth of parent levels.
|
|
282
|
-
Any variables that cannot be replaced by a suitable position value will remain unchanged.
|
|
283
|
-
""",
|
|
284
|
-
)
|
|
274
|
+
"""Base class for forms for components that can be assigned to either a device or a module."""
|
|
285
275
|
|
|
286
276
|
|
|
287
277
|
#
|
|
@@ -1060,11 +1050,27 @@ class ComponentTemplateCreateForm(ComponentForm):
|
|
|
1060
1050
|
description = forms.CharField(required=False)
|
|
1061
1051
|
|
|
1062
1052
|
|
|
1063
|
-
class ModularComponentTemplateCreateForm(
|
|
1053
|
+
class ModularComponentTemplateCreateForm(ComponentTemplateCreateForm):
|
|
1064
1054
|
"""
|
|
1065
1055
|
Base form for the creation of modular device component templates (subclassed from ModularComponentTemplateModel).
|
|
1066
1056
|
"""
|
|
1067
1057
|
|
|
1058
|
+
name_pattern = ExpandableNameField(
|
|
1059
|
+
label="Name",
|
|
1060
|
+
help_text="""
|
|
1061
|
+
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
1062
|
+
are not supported. Examples:
|
|
1063
|
+
<ul>
|
|
1064
|
+
<li><code>[ge,xe]-0/0/[0-9]</code></li>
|
|
1065
|
+
<li><code>e[0-3][a-d,f]</code></li>
|
|
1066
|
+
</ul>
|
|
1067
|
+
|
|
1068
|
+
The variables <code>{module}</code>, <code>{module.parent}</code>, <code>{module.parent.parent}</code>, etc.
|
|
1069
|
+
may be used in the name field and will be replaced by the <code>position</code> of the module bay that the
|
|
1070
|
+
module occupies (skipping over any bays with a blank <code>position</code>). These variables can be used
|
|
1071
|
+
multiple times in the component name and there is no limit to the depth of parent levels.
|
|
1072
|
+
Any variables that cannot be replaced by a suitable position value will remain unchanged.""",
|
|
1073
|
+
)
|
|
1068
1074
|
device_type = DynamicModelChoiceField(
|
|
1069
1075
|
queryset=DeviceType.objects.all(),
|
|
1070
1076
|
required=False,
|
|
@@ -1073,7 +1079,6 @@ class ModularComponentTemplateCreateForm(ModularComponentForm):
|
|
|
1073
1079
|
queryset=ModuleType.objects.all(),
|
|
1074
1080
|
required=False,
|
|
1075
1081
|
)
|
|
1076
|
-
description = forms.CharField(required=False)
|
|
1077
1082
|
|
|
1078
1083
|
|
|
1079
1084
|
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
|
@@ -1566,10 +1571,10 @@ class ModuleBayBaseCreateForm(BootstrapMixin, forms.Form):
|
|
|
1566
1571
|
required=False,
|
|
1567
1572
|
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)",
|
|
1568
1573
|
)
|
|
1569
|
-
position_pattern =
|
|
1570
|
-
label="Position",
|
|
1574
|
+
position_pattern = AutoPositionPatternField(
|
|
1571
1575
|
required=False,
|
|
1572
|
-
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)"
|
|
1576
|
+
help_text="Alphanumeric ranges are supported. (Must match the number of names being created.)"
|
|
1577
|
+
" Default to the names of the module bays unless manually supplied by the user.",
|
|
1573
1578
|
)
|
|
1574
1579
|
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
1575
1580
|
|
|
@@ -2538,12 +2543,8 @@ class DeviceBulkAddComponentForm(ComponentForm, CustomFieldModelBulkEditFormMixi
|
|
|
2538
2543
|
nullable_fields = []
|
|
2539
2544
|
|
|
2540
2545
|
|
|
2541
|
-
class ModuleBulkAddComponentForm(
|
|
2546
|
+
class ModuleBulkAddComponentForm(DeviceBulkAddComponentForm):
|
|
2542
2547
|
pk = forms.ModelMultipleChoiceField(queryset=Module.objects.all(), widget=forms.MultipleHiddenInput())
|
|
2543
|
-
description = forms.CharField(max_length=CHARFIELD_MAX_LENGTH, required=False)
|
|
2544
|
-
|
|
2545
|
-
class Meta:
|
|
2546
|
-
nullable_fields = []
|
|
2547
2548
|
|
|
2548
2549
|
|
|
2549
2550
|
#
|
|
@@ -3562,6 +3563,12 @@ class ModuleBayFilterForm(NautobotFilterForm):
|
|
|
3562
3563
|
|
|
3563
3564
|
|
|
3564
3565
|
class ModuleBayForm(NautobotModelForm):
|
|
3566
|
+
position = AutoPositionField(
|
|
3567
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
3568
|
+
help_text="The position of the module bay within the parent device/module. "
|
|
3569
|
+
"Defaults to the name of the module bay unless overridden.",
|
|
3570
|
+
required=False,
|
|
3571
|
+
)
|
|
3565
3572
|
parent_device = DynamicModelChoiceField(
|
|
3566
3573
|
queryset=Device.objects.all(),
|
|
3567
3574
|
required=False,
|
|
@@ -727,19 +727,22 @@ class Interface(ModularComponentModel, CableTermination, PathEndpoint, BaseInter
|
|
|
727
727
|
)
|
|
728
728
|
|
|
729
729
|
# Validate untagged VLAN
|
|
730
|
-
|
|
731
|
-
|
|
730
|
+
location = self.parent.location if self.parent is not None else None
|
|
731
|
+
if location:
|
|
732
|
+
location_ids = location.ancestors(include_self=True).values_list("id", flat=True)
|
|
733
|
+
else:
|
|
734
|
+
location_ids = []
|
|
732
735
|
if (
|
|
733
736
|
self.untagged_vlan
|
|
734
737
|
and self.untagged_vlan.locations.exists()
|
|
735
738
|
and self.parent
|
|
736
|
-
and not self.untagged_vlan.locations.filter(
|
|
739
|
+
and not self.untagged_vlan.locations.filter(pk__in=location_ids).exists()
|
|
737
740
|
):
|
|
738
741
|
raise ValidationError(
|
|
739
742
|
{
|
|
740
743
|
"untagged_vlan": (
|
|
741
744
|
f"The untagged VLAN ({self.untagged_vlan}) must have a common location as the interface's parent "
|
|
742
|
-
f"device, or it must be global."
|
|
745
|
+
f"device, or is in one of the parents of the interface's parent device's location, or it must be global."
|
|
743
746
|
)
|
|
744
747
|
}
|
|
745
748
|
)
|
|
@@ -1277,3 +1280,8 @@ class ModuleBay(PrimaryModel):
|
|
|
1277
1280
|
|
|
1278
1281
|
if not (self.parent_device or self.parent_module):
|
|
1279
1282
|
raise ValidationError("Either parent_device or parent_module must be set")
|
|
1283
|
+
|
|
1284
|
+
# Populate the position field with the name of the module bay if it is not supplied by the user.
|
|
1285
|
+
|
|
1286
|
+
if not self.position:
|
|
1287
|
+
self.position = self.name
|
nautobot/dcim/tables/devices.py
CHANGED
|
@@ -173,6 +173,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
173
173
|
template_code="""{% if record.device_redundancy_group %}<span class="badge badge-default">{{ record.device_redundancy_group_priority|default:'None' }}</span>{% else %}—{% endif %}"""
|
|
174
174
|
)
|
|
175
175
|
controller_managed_device_group = tables.Column(linkify=True)
|
|
176
|
+
software_version = tables.Column(linkify=True, verbose_name="Software Version")
|
|
176
177
|
secrets_group = tables.Column(linkify=True)
|
|
177
178
|
tags = TagColumn(url_name="dcim:device_list")
|
|
178
179
|
|
|
@@ -201,6 +202,7 @@ class DeviceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
201
202
|
"vc_priority",
|
|
202
203
|
"device_redundancy_group",
|
|
203
204
|
"device_redundancy_group_priority",
|
|
205
|
+
"software_version",
|
|
204
206
|
"controller_managed_device_group",
|
|
205
207
|
"secrets_group",
|
|
206
208
|
"tags",
|
|
@@ -616,7 +618,7 @@ class DeviceModulePowerOutletTable(PowerOutletTable):
|
|
|
616
618
|
row_attrs = {"class": cable_status_color_css}
|
|
617
619
|
|
|
618
620
|
|
|
619
|
-
class BaseInterfaceTable(BaseTable):
|
|
621
|
+
class BaseInterfaceTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
620
622
|
enabled = BooleanColumn()
|
|
621
623
|
ip_addresses = tables.TemplateColumn(
|
|
622
624
|
template_code=INTERFACE_IPADDRESSES,
|
|
@@ -632,7 +634,7 @@ class BaseInterfaceTable(BaseTable):
|
|
|
632
634
|
vrf = tables.Column(linkify=True, verbose_name="VRF")
|
|
633
635
|
|
|
634
636
|
|
|
635
|
-
class InterfaceTable(
|
|
637
|
+
class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpointTable):
|
|
636
638
|
mgmt_only = BooleanColumn()
|
|
637
639
|
tags = TagColumn(url_name="dcim:interface_list")
|
|
638
640
|
|
nautobot/dcim/tests/test_api.py
CHANGED
|
@@ -2198,6 +2198,34 @@ class InterfaceTest(Mixins.ModularDeviceComponentMixin, Mixins.BasePortTestMixin
|
|
|
2198
2198
|
self.client.post(url, self.untagged_vlan_data, format="json", **self.header), status.HTTP_201_CREATED
|
|
2199
2199
|
)
|
|
2200
2200
|
|
|
2201
|
+
def test_tagged_vlan_must_be_in_the_location_or_parent_locations_of_the_parent_device(self):
|
|
2202
|
+
self.add_permissions("dcim.add_interface")
|
|
2203
|
+
|
|
2204
|
+
interface_status = Status.objects.get_for_model(Interface).first()
|
|
2205
|
+
location = self.devices[0].location
|
|
2206
|
+
location_ids = [ancestor.id for ancestor in location.ancestors()]
|
|
2207
|
+
non_valid_locations = Location.objects.exclude(pk__in=location_ids)
|
|
2208
|
+
faulty_vlan = self.vlans[0]
|
|
2209
|
+
faulty_vlan.locations.set([non_valid_locations.first().pk])
|
|
2210
|
+
faulty_vlan.validated_save()
|
|
2211
|
+
faulty_data = {
|
|
2212
|
+
"device": self.devices[0].pk,
|
|
2213
|
+
"name": "Test Vlans Interface",
|
|
2214
|
+
"type": "virtual",
|
|
2215
|
+
"status": interface_status.pk,
|
|
2216
|
+
"mode": InterfaceModeChoices.MODE_TAGGED,
|
|
2217
|
+
"parent_interface": self.interfaces[1].pk,
|
|
2218
|
+
"tagged_vlans": [faulty_vlan.pk, self.vlans[1].pk],
|
|
2219
|
+
"untagged_vlan": self.vlans[2].pk,
|
|
2220
|
+
}
|
|
2221
|
+
response = self.client.post(self._get_list_url(), data=faulty_data, format="json", **self.header)
|
|
2222
|
+
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
|
2223
|
+
self.assertIn(
|
|
2224
|
+
b"must have the same location as the interface's parent device, or is in one of the parents of the interface's parent device's location, or "
|
|
2225
|
+
b"it must be global.",
|
|
2226
|
+
response.content,
|
|
2227
|
+
)
|
|
2228
|
+
|
|
2201
2229
|
def test_interface_belonging_to_common_device_or_vc_allowed(self):
|
|
2202
2230
|
"""Test parent, bridge, and LAG interfaces belonging to common device or VC is valid"""
|
|
2203
2231
|
self.add_permissions("dcim.add_interface")
|
|
@@ -341,9 +341,25 @@ class InterfaceTestCase(TestCase):
|
|
|
341
341
|
"tagged_vlans": [cls.vlan.pk],
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
def test_interface_form_clean_vlan_location_success(self):
|
|
345
|
+
"""Assert that form validation succeeds when matching locations/parent locations are associated to tagged VLAN"""
|
|
346
|
+
location = self.device.location
|
|
347
|
+
location_ids = location.ancestors(include_self=True).values_list("id", flat=True)
|
|
348
|
+
self.vlan.locations.set([location.id])
|
|
349
|
+
self.data["tagged_vlans"] = [self.vlan]
|
|
350
|
+
form = InterfaceForm(data=self.data, instance=self.interface)
|
|
351
|
+
self.assertTrue(form.is_valid())
|
|
352
|
+
self.vlan.locations.set(location_ids[:2])
|
|
353
|
+
self.data["tagged_vlans"] = [self.vlan]
|
|
354
|
+
form = InterfaceForm(data=self.data, instance=self.interface)
|
|
355
|
+
self.assertTrue(form.is_valid())
|
|
356
|
+
|
|
344
357
|
def test_interface_form_clean_vlan_location_fail(self):
|
|
345
358
|
"""Assert that form validation fails when no matching locations are associated to tagged VLAN"""
|
|
346
|
-
|
|
359
|
+
location = self.device.location
|
|
360
|
+
location_ids = location.ancestors(include_self=True).values_list("id", flat=True)
|
|
361
|
+
self.vlan.locations.set(list(Location.objects.exclude(pk__in=location_ids))[:2])
|
|
362
|
+
self.data["tagged_vlans"] = [self.vlan]
|
|
347
363
|
form = InterfaceForm(data=self.data, instance=self.interface)
|
|
348
364
|
self.assertFalse(form.is_valid())
|
|
349
365
|
|
|
@@ -2376,19 +2376,57 @@ class InterfaceTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
|
|
|
2376
2376
|
)
|
|
2377
2377
|
|
|
2378
2378
|
def test_error_raised_when_adding_tagged_vlan_with_different_location_from_interface_parent_location(self):
|
|
2379
|
+
intf_status = Status.objects.get_for_model(Interface).first()
|
|
2380
|
+
intf_role = Role.objects.get_for_model(Interface).first()
|
|
2381
|
+
location_type = LocationType.objects.get(name="Campus")
|
|
2382
|
+
child_location = Location.objects.filter(parent__isnull=False, location_type=location_type).first()
|
|
2383
|
+
self.device.location = child_location
|
|
2384
|
+
self.device.validated_save()
|
|
2385
|
+
# Same location as the device
|
|
2386
|
+
interface = Interface.objects.create(
|
|
2387
|
+
name="Test Interface 2",
|
|
2388
|
+
mode=InterfaceModeChoices.MODE_TAGGED,
|
|
2389
|
+
device=self.device,
|
|
2390
|
+
status=intf_status,
|
|
2391
|
+
role=intf_role,
|
|
2392
|
+
)
|
|
2393
|
+
self.other_location_vlan.locations.set([self.device.location.pk])
|
|
2394
|
+
interface.tagged_vlans.set([self.other_location_vlan.pk])
|
|
2395
|
+
|
|
2396
|
+
# One of the parent locations of the device's location
|
|
2397
|
+
interface = Interface.objects.create(
|
|
2398
|
+
name="Test Interface 3",
|
|
2399
|
+
mode=InterfaceModeChoices.MODE_TAGGED,
|
|
2400
|
+
device=self.device,
|
|
2401
|
+
status=intf_status,
|
|
2402
|
+
role=intf_role,
|
|
2403
|
+
)
|
|
2404
|
+
self.other_location_vlan.locations.set([self.device.location.ancestors().first().pk])
|
|
2405
|
+
interface.tagged_vlans.set([self.other_location_vlan.pk])
|
|
2406
|
+
|
|
2379
2407
|
with self.assertRaises(ValidationError) as err:
|
|
2380
2408
|
interface = Interface.objects.create(
|
|
2381
|
-
name="Test Interface",
|
|
2409
|
+
name="Test Interface 1",
|
|
2382
2410
|
mode=InterfaceModeChoices.MODE_TAGGED,
|
|
2383
2411
|
device=self.device,
|
|
2384
|
-
status=
|
|
2385
|
-
role=
|
|
2412
|
+
status=intf_status,
|
|
2413
|
+
role=intf_role,
|
|
2386
2414
|
)
|
|
2415
|
+
location_3 = Location.objects.create(
|
|
2416
|
+
name="Invalid VLAN Location",
|
|
2417
|
+
location_type=LocationType.objects.get(name="Campus"),
|
|
2418
|
+
status=Status.objects.get_for_model(Location).first(),
|
|
2419
|
+
)
|
|
2420
|
+
# clear the valid locations
|
|
2421
|
+
self.other_location_vlan.locations.set([])
|
|
2422
|
+
# assign the invalid location
|
|
2423
|
+
self.other_location_vlan.location = location_3
|
|
2424
|
+
self.other_location_vlan.validated_save()
|
|
2387
2425
|
interface.tagged_vlans.add(self.other_location_vlan)
|
|
2388
2426
|
self.assertEqual(
|
|
2389
2427
|
err.exception.message_dict["tagged_vlans"][0],
|
|
2390
2428
|
f"Tagged VLAN with names {[self.other_location_vlan.name]} must all belong to the "
|
|
2391
|
-
f"same location as the interface's parent device, or it must be global.",
|
|
2429
|
+
f"same location as the interface's parent device, one of the parent locations of the interface's parent device's location, or it must be global.",
|
|
2392
2430
|
)
|
|
2393
2431
|
|
|
2394
2432
|
def test_add_ip_addresses(self):
|
|
@@ -2811,6 +2849,22 @@ class ModuleBayTestCase(ModularDeviceComponentTestCaseMixin, ModelTestCases.Base
|
|
|
2811
2849
|
self.assertIsNone(child_module_bay.parent)
|
|
2812
2850
|
self.assertIsNone(grandchild_module_bay.parent)
|
|
2813
2851
|
|
|
2852
|
+
def test_position_value_auto_population(self):
|
|
2853
|
+
"""
|
|
2854
|
+
Assert that the value of the module bay position is auto-populated by its name if position is not provided by the user.
|
|
2855
|
+
"""
|
|
2856
|
+
|
|
2857
|
+
module_bay = ModuleBay.objects.create(
|
|
2858
|
+
parent_device=self.device,
|
|
2859
|
+
name="1111",
|
|
2860
|
+
)
|
|
2861
|
+
module_bay.validated_save()
|
|
2862
|
+
self.assertEqual(module_bay.position, module_bay.name)
|
|
2863
|
+
# Test the default value is overriden if the user provides a position value.
|
|
2864
|
+
module_bay.position = "1222"
|
|
2865
|
+
module_bay.validated_save()
|
|
2866
|
+
self.assertEqual(module_bay.position, "1222")
|
|
2867
|
+
|
|
2814
2868
|
|
|
2815
2869
|
class ModuleBayTemplateTestCase(ModularDeviceComponentTemplateTestCaseMixin, ModelTestCases.BaseModelTestCase):
|
|
2816
2870
|
model = ModuleBayTemplate
|