nautobot 2.3.13__py3-none-any.whl → 2.3.14__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/circuits/tables.py +2 -1
- nautobot/core/filters.py +2 -0
- nautobot/core/jobs/cleanup.py +47 -11
- nautobot/core/templates/search.html +7 -0
- nautobot/core/testing/filters.py +20 -5
- nautobot/dcim/forms.py +6 -5
- nautobot/dcim/models/devices.py +1 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/extras/forms/mixins.py +10 -2
- nautobot/extras/jobs.py +6 -4
- nautobot/ipam/models.py +62 -11
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/tests/test_api.py +68 -1
- nautobot/ipam/tests/test_models.py +41 -0
- nautobot/project-static/docs/404.html +1 -1
- nautobot/project-static/docs/apps/index.html +1 -1
- nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +7 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1 -1
- nautobot/project-static/docs/development/apps/api/configuration-view.html +1 -1
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/global-search.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/graphql.html +1 -1
- nautobot/project-static/docs/development/apps/api/models/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +1 -1
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +1 -1
- nautobot/project-static/docs/development/apps/api/prometheus.html +1 -1
- nautobot/project-static/docs/development/apps/api/setup.html +1 -1
- nautobot/project-static/docs/development/apps/api/testing.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +1 -1
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/base-template.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/index.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/notes.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +1 -1
- nautobot/project-static/docs/development/apps/api/views/urls.html +1 -1
- nautobot/project-static/docs/development/apps/index.html +1 -1
- nautobot/project-static/docs/development/apps/migration/code-updates.html +1 -1
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -1
- nautobot/project-static/docs/development/apps/migration/from-v1.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +1 -1
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +1 -1
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +1 -1
- nautobot/project-static/docs/development/core/application-registry.html +1 -1
- nautobot/project-static/docs/development/core/best-practices.html +1 -1
- nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
- nautobot/project-static/docs/development/core/caching.html +1 -1
- nautobot/project-static/docs/development/core/controllers.html +1 -1
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +1 -1
- nautobot/project-static/docs/development/core/generic-views.html +1 -1
- nautobot/project-static/docs/development/core/getting-started.html +154 -140
- nautobot/project-static/docs/development/core/homepage.html +1 -1
- nautobot/project-static/docs/development/core/index.html +1 -1
- nautobot/project-static/docs/development/core/model-checklist.html +1 -1
- nautobot/project-static/docs/development/core/model-features.html +1 -1
- nautobot/project-static/docs/development/core/natural-keys.html +1 -1
- nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
- nautobot/project-static/docs/development/core/release-checklist.html +1 -1
- nautobot/project-static/docs/development/core/role-internals.html +1 -1
- nautobot/project-static/docs/development/core/settings.html +1 -1
- nautobot/project-static/docs/development/core/style-guide.html +1 -1
- nautobot/project-static/docs/development/core/templates.html +1 -1
- nautobot/project-static/docs/development/core/testing.html +1 -1
- nautobot/project-static/docs/development/core/user-preferences.html +1 -1
- nautobot/project-static/docs/development/index.html +1 -1
- nautobot/project-static/docs/development/jobs/index.html +142 -118
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +1 -1
- nautobot/project-static/docs/index.html +1 -1
- nautobot/project-static/docs/overview/application_stack.html +1 -1
- nautobot/project-static/docs/overview/design_philosophy.html +1 -1
- nautobot/project-static/docs/release-notes/index.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.0.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.1.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.2.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.4.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.5.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.6.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.0.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.1.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.3.html +332 -181
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +270 -270
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -1
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +1 -1
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +1 -1
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -1
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +1 -1
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +1 -1
- nautobot/project-static/docs/user-guide/index.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +1 -1
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +1 -1
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/METADATA +1 -1
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/RECORD +295 -295
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/NOTICE +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/WHEEL +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.14.dist-info}/entry_points.txt +0 -0
nautobot/circuits/tables.py
CHANGED
|
@@ -110,6 +110,7 @@ class CircuitTable(StatusTableMixin, BaseTable):
|
|
|
110
110
|
pk = ToggleColumn()
|
|
111
111
|
cid = tables.LinkColumn(verbose_name="ID")
|
|
112
112
|
provider = tables.Column(linkify=True)
|
|
113
|
+
circuit_type = tables.Column(linkify=True)
|
|
113
114
|
tenant = TenantColumn()
|
|
114
115
|
tags = TagColumn(url_name="circuits:circuit_list")
|
|
115
116
|
|
|
@@ -146,7 +147,7 @@ class CircuitTable(StatusTableMixin, BaseTable):
|
|
|
146
147
|
"pk",
|
|
147
148
|
"cid",
|
|
148
149
|
"provider",
|
|
149
|
-
"
|
|
150
|
+
"circuit_type",
|
|
150
151
|
"status",
|
|
151
152
|
"tenant",
|
|
152
153
|
"circuit_termination_a",
|
nautobot/core/filters.py
CHANGED
|
@@ -618,6 +618,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
618
618
|
@staticmethod
|
|
619
619
|
def _get_filter_lookup_dict(existing_filter):
|
|
620
620
|
# Choose the lookup expression map based on the filter type
|
|
621
|
+
|
|
621
622
|
if isinstance(
|
|
622
623
|
existing_filter,
|
|
623
624
|
(
|
|
@@ -637,6 +638,7 @@ class BaseFilterSet(django_filters.FilterSet):
|
|
|
637
638
|
(
|
|
638
639
|
django_filters.ModelChoiceFilter,
|
|
639
640
|
django_filters.ModelMultipleChoiceFilter,
|
|
641
|
+
MultiValueUUIDFilter,
|
|
640
642
|
TagFilter,
|
|
641
643
|
TreeNodeMultipleChoiceFilter,
|
|
642
644
|
),
|
nautobot/core/jobs/cleanup.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
|
|
3
3
|
from django.core.exceptions import PermissionDenied
|
|
4
|
+
from django.db.models import CASCADE
|
|
4
5
|
from django.db.models.signals import pre_delete
|
|
5
6
|
from django.utils import timezone
|
|
6
7
|
|
|
@@ -48,6 +49,27 @@ class LogsCleanup(Job):
|
|
|
48
49
|
description = "Delete ObjectChange and/or JobResult/JobLogEntry records older than a specified cutoff."
|
|
49
50
|
has_sensitive_variables = False
|
|
50
51
|
|
|
52
|
+
def recursive_delete_with_cascade(self, queryset, deletion_summary):
|
|
53
|
+
"""
|
|
54
|
+
Recursively deletes all related objects with CASCADE for a given queryset.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
queryset (QuerySet): The queryset of objects to delete.
|
|
58
|
+
deletion_summary (dict): A dictionary to store the count of deleted objects for each model.
|
|
59
|
+
"""
|
|
60
|
+
related_objects = queryset.model._meta.related_objects
|
|
61
|
+
queryset = queryset.only("id")
|
|
62
|
+
|
|
63
|
+
for related_object in related_objects:
|
|
64
|
+
if related_object.on_delete is CASCADE:
|
|
65
|
+
related_model = related_object.related_model
|
|
66
|
+
related_field_name = related_object.field.name
|
|
67
|
+
cascade_queryset = related_model.objects.filter(**{f"{related_field_name}__id__in": queryset})
|
|
68
|
+
self.recursive_delete_with_cascade(cascade_queryset, deletion_summary)
|
|
69
|
+
_, deleted_dict = queryset.delete()
|
|
70
|
+
deletion_summary.update(deleted_dict)
|
|
71
|
+
return deletion_summary
|
|
72
|
+
|
|
51
73
|
def run(self, *, cleanup_types, max_age=None):
|
|
52
74
|
if max_age in (None, ""):
|
|
53
75
|
max_age = get_settings_or_config("CHANGELOG_RETENTION")
|
|
@@ -77,22 +99,36 @@ class LogsCleanup(Job):
|
|
|
77
99
|
|
|
78
100
|
if CleanupTypes.JOB_RESULT in cleanup_types:
|
|
79
101
|
self.logger.info("Deleting JobResult records prior to %s", cutoff)
|
|
80
|
-
|
|
102
|
+
queryset = JobResult.objects.restrict(self.user, "delete").filter(date_done__lt=cutoff)
|
|
103
|
+
deletion_summary = {}
|
|
104
|
+
self.recursive_delete_with_cascade(queryset, deletion_summary)
|
|
81
105
|
result.setdefault("extras.JobResult", 0)
|
|
82
106
|
result.setdefault("extras.JobLogEntry", 0)
|
|
83
|
-
result.update(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
result.update(deletion_summary)
|
|
108
|
+
|
|
109
|
+
for modelname, count in deletion_summary.items():
|
|
110
|
+
self.logger.info(
|
|
111
|
+
"As part of deleting %d JobResult records, also deleted %d related %s records",
|
|
112
|
+
result["extras.JobResult"],
|
|
113
|
+
count,
|
|
114
|
+
modelname,
|
|
115
|
+
)
|
|
89
116
|
|
|
90
117
|
if CleanupTypes.OBJECT_CHANGE in cleanup_types:
|
|
91
118
|
self.logger.info("Deleting ObjectChange records prior to %s", cutoff)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
queryset = ObjectChange.objects.restrict(self.user, "delete").filter(time__lt=cutoff)
|
|
120
|
+
deletion_summary = {}
|
|
121
|
+
self.recursive_delete_with_cascade(queryset, deletion_summary)
|
|
122
|
+
result.setdefault("extras.ObjectChange", 0)
|
|
123
|
+
result.update(deletion_summary)
|
|
124
|
+
|
|
125
|
+
for modelname, count in deletion_summary.items():
|
|
126
|
+
self.logger.info(
|
|
127
|
+
"As part of deleting %d ObjectChange records, also deleted %d related %s records",
|
|
128
|
+
result["extras.ObjectChange"],
|
|
129
|
+
count,
|
|
130
|
+
modelname,
|
|
131
|
+
)
|
|
96
132
|
return result
|
|
97
133
|
finally:
|
|
98
134
|
# Be sure to clean up after ourselves!
|
nautobot/core/testing/filters.py
CHANGED
|
@@ -88,11 +88,26 @@ class FilterTestCases:
|
|
|
88
88
|
return self.filterset.declared_filters["q"].filter_predicates
|
|
89
89
|
|
|
90
90
|
def test_id(self):
|
|
91
|
-
"""Verify that the filterset supports filtering by id
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
"""Verify that the filterset supports filtering by id with only lookup `__n`."""
|
|
92
|
+
with self.subTest("Assert `id`"):
|
|
93
|
+
params = {"id": list(self.queryset.values_list("pk", flat=True)[:2])}
|
|
94
|
+
expected_queryset = self.queryset.filter(id__in=params["id"])
|
|
95
|
+
filterset = self.filterset(params, self.queryset)
|
|
96
|
+
self.assertTrue(filterset.is_valid())
|
|
97
|
+
self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
|
|
98
|
+
|
|
99
|
+
with self.subTest("Assert negate lookup"):
|
|
100
|
+
params = {"id__n": list(self.queryset.values_list("pk", flat=True)[:2])}
|
|
101
|
+
expected_queryset = self.queryset.exclude(id__in=params["id__n"])
|
|
102
|
+
filterset = self.filterset(params, self.queryset)
|
|
103
|
+
self.assertTrue(filterset.is_valid())
|
|
104
|
+
self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
|
|
105
|
+
|
|
106
|
+
with self.subTest("Assert invalid lookup"):
|
|
107
|
+
params = {"id__in": list(self.queryset.values_list("pk", flat=True)[:2])}
|
|
108
|
+
filterset = self.filterset(params, self.queryset)
|
|
109
|
+
self.assertFalse(filterset.is_valid())
|
|
110
|
+
self.assertIn("Unknown filter field", filterset.errors.as_text())
|
|
96
111
|
|
|
97
112
|
def test_invalid_filter(self):
|
|
98
113
|
"""Verify that the filterset reports as invalid when initialized with an unsupported filter parameter."""
|
nautobot/dcim/forms.py
CHANGED
|
@@ -792,7 +792,7 @@ class DeviceFamilyFilterForm(NautobotFilterForm):
|
|
|
792
792
|
tags = TagFilterField(model)
|
|
793
793
|
|
|
794
794
|
|
|
795
|
-
class DeviceFamilyBulkEditForm(
|
|
795
|
+
class DeviceFamilyBulkEditForm(TagsBulkEditFormMixin, NautobotBulkEditForm):
|
|
796
796
|
pk = forms.ModelMultipleChoiceField(queryset=DeviceFamily.objects.all(), widget=forms.MultipleHiddenInput())
|
|
797
797
|
description = forms.CharField(required=False)
|
|
798
798
|
|
|
@@ -3183,9 +3183,6 @@ class InterfaceBulkEditForm(
|
|
|
3183
3183
|
untagged_vlan = DynamicModelChoiceField(
|
|
3184
3184
|
queryset=VLAN.objects.all(),
|
|
3185
3185
|
required=False,
|
|
3186
|
-
query_params={
|
|
3187
|
-
"locations": "null",
|
|
3188
|
-
},
|
|
3189
3186
|
)
|
|
3190
3187
|
tagged_vlans = DynamicModelMultipleChoiceField(
|
|
3191
3188
|
queryset=VLAN.objects.all(),
|
|
@@ -3235,8 +3232,12 @@ class InterfaceBulkEditForm(
|
|
|
3235
3232
|
# Limit VLAN choices by Location
|
|
3236
3233
|
if locations.count() == 1:
|
|
3237
3234
|
location = locations.first()
|
|
3238
|
-
|
|
3235
|
+
# In the case of a single location, use the available_on_device query param to limit untagged VLAN choices
|
|
3236
|
+
# to those available on the devices in that location and in the ancestors of the location.
|
|
3237
|
+
self.fields["untagged_vlan"].widget.add_query_param("available_on_device", device.pk)
|
|
3239
3238
|
self.fields["tagged_vlans"].widget.add_query_param("locations", location.pk)
|
|
3239
|
+
else:
|
|
3240
|
+
self.fields["tagged_vlans"].widget.add_query_param("locations", "null")
|
|
3240
3241
|
|
|
3241
3242
|
# Restrict parent/bridge/LAG interface assignment by device (or VC master)
|
|
3242
3243
|
if device_count == 1:
|
nautobot/dcim/models/devices.py
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
from django.test import TestCase
|
|
2
2
|
|
|
3
3
|
from nautobot.core.testing.forms import FormTestCases
|
|
4
|
+
from nautobot.core.testing.mixins import NautobotTestCaseMixin
|
|
4
5
|
from nautobot.dcim.choices import DeviceFaceChoices, InterfaceModeChoices, InterfaceTypeChoices, RackWidthChoices
|
|
5
|
-
from nautobot.dcim.forms import
|
|
6
|
+
from nautobot.dcim.forms import (
|
|
7
|
+
DeviceFilterForm,
|
|
8
|
+
DeviceForm,
|
|
9
|
+
InterfaceBulkEditForm,
|
|
10
|
+
InterfaceCreateForm,
|
|
11
|
+
InterfaceForm,
|
|
12
|
+
RackForm,
|
|
13
|
+
)
|
|
6
14
|
from nautobot.dcim.models import (
|
|
7
15
|
Device,
|
|
8
16
|
DeviceType,
|
|
@@ -320,7 +328,7 @@ class RackTestCase(TestCase):
|
|
|
320
328
|
self.assertTrue(form.is_valid())
|
|
321
329
|
|
|
322
330
|
|
|
323
|
-
class InterfaceTestCase(TestCase):
|
|
331
|
+
class InterfaceTestCase(NautobotTestCaseMixin, TestCase):
|
|
324
332
|
@classmethod
|
|
325
333
|
def setUpTestData(cls):
|
|
326
334
|
cls.device = Device.objects.first()
|
|
@@ -380,3 +388,44 @@ class InterfaceTestCase(TestCase):
|
|
|
380
388
|
self.vlan.locations.clear()
|
|
381
389
|
form = InterfaceForm(data=self.data, instance=self.interface)
|
|
382
390
|
self.assertTrue(form.is_valid())
|
|
391
|
+
|
|
392
|
+
def test_untagged_vlans_dropdown_options_align_in_interface_edit_form_and_bulk_edit_form(self):
|
|
393
|
+
"""
|
|
394
|
+
Assert that untagged_vlans field dropdown are populated correctly in InterfaceForm and InterfaceBulkEditForm,
|
|
395
|
+
and that the queryset is the same for both forms.
|
|
396
|
+
"""
|
|
397
|
+
status = Status.objects.get_for_model(Interface).first()
|
|
398
|
+
location = Location.objects.filter(location_type=LocationType.objects.get(name="Campus")).first()
|
|
399
|
+
devices = Device.objects.all()[:3]
|
|
400
|
+
for device in devices:
|
|
401
|
+
device.location = location
|
|
402
|
+
device.save()
|
|
403
|
+
interfaces = (
|
|
404
|
+
Interface.objects.create(
|
|
405
|
+
device=devices[0],
|
|
406
|
+
name="Test Interface 1",
|
|
407
|
+
type=InterfaceTypeChoices.TYPE_2GFC_SFP,
|
|
408
|
+
status=status,
|
|
409
|
+
),
|
|
410
|
+
Interface.objects.create(
|
|
411
|
+
device=devices[1],
|
|
412
|
+
name="Test Interface 2",
|
|
413
|
+
type=InterfaceTypeChoices.TYPE_LAG,
|
|
414
|
+
status=status,
|
|
415
|
+
),
|
|
416
|
+
Interface.objects.create(
|
|
417
|
+
device=devices[2],
|
|
418
|
+
name="Test Interface 3",
|
|
419
|
+
type=InterfaceTypeChoices.TYPE_100ME_FIXED,
|
|
420
|
+
status=status,
|
|
421
|
+
),
|
|
422
|
+
)
|
|
423
|
+
edit_form = InterfaceForm(data=self.data, instance=interfaces[0])
|
|
424
|
+
bulk_edit_form = InterfaceBulkEditForm(
|
|
425
|
+
model=Interface,
|
|
426
|
+
data={"pks": [interface.pk for interface in interfaces]},
|
|
427
|
+
)
|
|
428
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
429
|
+
edit_form.fields["untagged_vlan"].queryset,
|
|
430
|
+
bulk_edit_form.fields["untagged_vlan"].queryset,
|
|
431
|
+
)
|
nautobot/extras/forms/mixins.py
CHANGED
|
@@ -842,8 +842,16 @@ class TagsBulkEditFormMixin(forms.Form):
|
|
|
842
842
|
super().__init__(*args, **kwargs)
|
|
843
843
|
|
|
844
844
|
# Add add/remove tags fields
|
|
845
|
-
self.fields["add_tags"] = DynamicModelMultipleChoiceField(
|
|
846
|
-
|
|
845
|
+
self.fields["add_tags"] = DynamicModelMultipleChoiceField(
|
|
846
|
+
queryset=Tag.objects.all(),
|
|
847
|
+
query_params={"content_types": self.model._meta.label_lower},
|
|
848
|
+
required=False,
|
|
849
|
+
)
|
|
850
|
+
self.fields["remove_tags"] = DynamicModelMultipleChoiceField(
|
|
851
|
+
queryset=Tag.objects.all(),
|
|
852
|
+
query_params={"content_types": self.model._meta.label_lower},
|
|
853
|
+
required=False,
|
|
854
|
+
)
|
|
847
855
|
|
|
848
856
|
|
|
849
857
|
# 2.2 TODO: Names below are only for backward compatibility with Nautobot 1.3 and earlier. Remove in 2.2
|
nautobot/extras/jobs.py
CHANGED
|
@@ -98,13 +98,15 @@ class BaseJob:
|
|
|
98
98
|
|
|
99
99
|
- name (str)
|
|
100
100
|
- description (str)
|
|
101
|
-
- hidden (bool)
|
|
102
|
-
- field_order (list)
|
|
103
101
|
- approval_required (bool)
|
|
104
|
-
-
|
|
105
|
-
-
|
|
102
|
+
- dryrun_default (bool)
|
|
103
|
+
- field_order (list)
|
|
106
104
|
- has_sensitive_variables (bool)
|
|
105
|
+
- hidden (bool)
|
|
106
|
+
- soft_time_limit (int)
|
|
107
107
|
- task_queues (list)
|
|
108
|
+
- template_name (str)
|
|
109
|
+
- time_limit (int)
|
|
108
110
|
"""
|
|
109
111
|
|
|
110
112
|
def __init__(self):
|
nautobot/ipam/models.py
CHANGED
|
@@ -531,8 +531,32 @@ class Prefix(PrimaryModel):
|
|
|
531
531
|
prefix = kwargs.pop("prefix", None)
|
|
532
532
|
self._location = kwargs.pop("location", None)
|
|
533
533
|
super().__init__(*args, **kwargs)
|
|
534
|
+
|
|
535
|
+
# Initialize cached fields
|
|
536
|
+
self._parent = None
|
|
537
|
+
self._network = None
|
|
538
|
+
self._prefix_length = None
|
|
539
|
+
self._namespace_id = None
|
|
540
|
+
|
|
534
541
|
self._deconstruct_prefix(prefix)
|
|
535
542
|
|
|
543
|
+
@staticmethod
|
|
544
|
+
def _extract_field_value(field_name, field_names, values):
|
|
545
|
+
for current_field, field_value in zip(field_names, values):
|
|
546
|
+
if field_name == current_field:
|
|
547
|
+
return field_value
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
@classmethod
|
|
551
|
+
def from_db(cls, db, field_names, values):
|
|
552
|
+
instance = super().from_db(db, field_names, values)
|
|
553
|
+
# These cached values are used to detect changes during save,
|
|
554
|
+
# avoiding unnecessary re-parenting of subnets and IPs if these fields have not been updated.
|
|
555
|
+
instance._network = cls._extract_field_value("network", field_names, values)
|
|
556
|
+
instance._prefix_length = cls._extract_field_value("prefix_length", field_names, values)
|
|
557
|
+
instance._namespace_id = cls._extract_field_value("namespace_id", field_names, values)
|
|
558
|
+
return instance
|
|
559
|
+
|
|
536
560
|
def __str__(self):
|
|
537
561
|
return str(self.prefix)
|
|
538
562
|
|
|
@@ -612,6 +636,23 @@ class Prefix(PrimaryModel):
|
|
|
612
636
|
protected_objects.update(parent=self.parent)
|
|
613
637
|
return super().delete(*args, **kwargs)
|
|
614
638
|
|
|
639
|
+
def get_parent(self):
|
|
640
|
+
# Determine if a parent exists and set it to the closest ancestor by `prefix_length`.
|
|
641
|
+
if self._parent is not None:
|
|
642
|
+
return self._parent
|
|
643
|
+
|
|
644
|
+
if supernets := self.supernets():
|
|
645
|
+
parent = max(supernets, key=operator.attrgetter("prefix_length"))
|
|
646
|
+
self._parent = parent
|
|
647
|
+
return parent
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
def clean(self):
|
|
651
|
+
if self.prefix:
|
|
652
|
+
# self.parent depends on self.prefix having a value
|
|
653
|
+
self.parent = self.get_parent()
|
|
654
|
+
super().clean()
|
|
655
|
+
|
|
615
656
|
def save(self, *args, **kwargs):
|
|
616
657
|
if isinstance(self.prefix, netaddr.IPNetwork):
|
|
617
658
|
# Clear host bits from prefix
|
|
@@ -619,11 +660,7 @@ class Prefix(PrimaryModel):
|
|
|
619
660
|
# which will (re)set the broadcast and ip_version values of this instance to their correct values.
|
|
620
661
|
self.prefix = self.prefix.cidr
|
|
621
662
|
|
|
622
|
-
|
|
623
|
-
supernets = self.supernets()
|
|
624
|
-
if supernets:
|
|
625
|
-
parent = max(supernets, key=operator.attrgetter("prefix_length"))
|
|
626
|
-
self.parent = parent
|
|
663
|
+
self.parent = self.get_parent()
|
|
627
664
|
|
|
628
665
|
# Validate that creation of this prefix does not create an invalid parent/child relationship
|
|
629
666
|
# 3.0 TODO: uncomment this to enforce this constraint
|
|
@@ -655,15 +692,26 @@ class Prefix(PrimaryModel):
|
|
|
655
692
|
# )
|
|
656
693
|
# raise ValidationError({"__all__": err_msg})
|
|
657
694
|
|
|
695
|
+
# cache the value of present_in_database; because after `super().save()`
|
|
696
|
+
# `self.present_in_database` would always return True`
|
|
697
|
+
present_in_database = self.present_in_database
|
|
698
|
+
|
|
658
699
|
with transaction.atomic():
|
|
659
700
|
super().save(*args, **kwargs)
|
|
660
701
|
if self._location is not None:
|
|
661
702
|
self.location = self._location
|
|
662
703
|
|
|
663
|
-
#
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
704
|
+
# Only reparent subnets and ips if any of these fields has been updated.
|
|
705
|
+
if (
|
|
706
|
+
not present_in_database
|
|
707
|
+
or self._network != self.network
|
|
708
|
+
or self._namespace_id != self.namespace_id
|
|
709
|
+
or self._prefix_length != self.prefix_length
|
|
710
|
+
):
|
|
711
|
+
# Determine the subnets and reparent them to this prefix.
|
|
712
|
+
self.reparent_subnets()
|
|
713
|
+
# Determine the child IPs and reparent them to this prefix.
|
|
714
|
+
self.reparent_ips()
|
|
667
715
|
|
|
668
716
|
@property
|
|
669
717
|
def cidr_str(self):
|
|
@@ -739,6 +787,7 @@ class Prefix(PrimaryModel):
|
|
|
739
787
|
Returns:
|
|
740
788
|
QuerySet
|
|
741
789
|
"""
|
|
790
|
+
|
|
742
791
|
query = Prefix.objects.all()
|
|
743
792
|
|
|
744
793
|
if for_update:
|
|
@@ -750,7 +799,7 @@ class Prefix(PrimaryModel):
|
|
|
750
799
|
if not include_self:
|
|
751
800
|
query = query.exclude(id=self.id)
|
|
752
801
|
|
|
753
|
-
|
|
802
|
+
supernets = query.filter(
|
|
754
803
|
ip_version=self.ip_version,
|
|
755
804
|
prefix_length__lte=self.prefix_length,
|
|
756
805
|
network__lte=self.network,
|
|
@@ -758,6 +807,8 @@ class Prefix(PrimaryModel):
|
|
|
758
807
|
namespace=self.namespace,
|
|
759
808
|
)
|
|
760
809
|
|
|
810
|
+
return supernets
|
|
811
|
+
|
|
761
812
|
def subnets(self, direct=False, include_self=False, for_update=False):
|
|
762
813
|
"""
|
|
763
814
|
Return subnets of this Prefix.
|
|
@@ -867,7 +918,7 @@ class Prefix(PrimaryModel):
|
|
|
867
918
|
Return all available IPs within this prefix as an IPSet.
|
|
868
919
|
"""
|
|
869
920
|
prefix = netaddr.IPSet(self.prefix)
|
|
870
|
-
child_ips = netaddr.IPSet([ip.address.ip for ip in self.
|
|
921
|
+
child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_all_ips()])
|
|
871
922
|
available_ips = prefix - child_ips
|
|
872
923
|
|
|
873
924
|
# IPv6, pool, or IPv4 /31-32 sets are fully usable
|
nautobot/ipam/tables.py
CHANGED
|
@@ -404,7 +404,7 @@ class PrefixDetailTable(PrefixTable):
|
|
|
404
404
|
"type",
|
|
405
405
|
"status",
|
|
406
406
|
"children",
|
|
407
|
-
|
|
407
|
+
"vrf_count",
|
|
408
408
|
"utilization",
|
|
409
409
|
"tenant",
|
|
410
410
|
"location_count",
|
|
@@ -420,7 +420,7 @@ class PrefixDetailTable(PrefixTable):
|
|
|
420
420
|
"type",
|
|
421
421
|
"status",
|
|
422
422
|
"children",
|
|
423
|
-
|
|
423
|
+
"vrf_count",
|
|
424
424
|
"utilization",
|
|
425
425
|
"tenant",
|
|
426
426
|
"location_count",
|
nautobot/ipam/tests/test_api.py
CHANGED
|
@@ -594,9 +594,76 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
594
594
|
response = self.client.get(url, **self.header)
|
|
595
595
|
self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.type = network
|
|
596
596
|
|
|
597
|
+
def test_list_available_ips_calculate_child_ips(self):
|
|
598
|
+
"""
|
|
599
|
+
Test retrieval of all available IP addresses when child IP's exists.
|
|
600
|
+
"""
|
|
601
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
602
|
+
prefix = Prefix.objects.create(
|
|
603
|
+
prefix="192.0.3.0/29",
|
|
604
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
605
|
+
namespace=self.namespace,
|
|
606
|
+
status=self.status,
|
|
607
|
+
)
|
|
608
|
+
Prefix.objects.create(
|
|
609
|
+
prefix="192.0.3.0/30",
|
|
610
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
611
|
+
namespace=self.namespace,
|
|
612
|
+
status=self.status,
|
|
613
|
+
)
|
|
614
|
+
IPAddress.objects.create(
|
|
615
|
+
address="192.0.3.1/30",
|
|
616
|
+
status=ip_status,
|
|
617
|
+
namespace=self.namespace,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
621
|
+
self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
|
|
622
|
+
|
|
623
|
+
# Retrieve all available IPs
|
|
624
|
+
response = self.client.get(url, **self.header)
|
|
625
|
+
self.assertEqual(len(response.data), 7) # 7 because prefix.type = pool got 8 IP's minus one children IP
|
|
626
|
+
|
|
627
|
+
def test_create_single_available_ip_calculate_child_ips(self):
|
|
628
|
+
"""
|
|
629
|
+
Test creating a single IP when child IP's exists.
|
|
630
|
+
"""
|
|
631
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
632
|
+
prefix = Prefix.objects.create(
|
|
633
|
+
prefix="192.0.4.0/31",
|
|
634
|
+
namespace=self.namespace,
|
|
635
|
+
type=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
636
|
+
status=self.status,
|
|
637
|
+
)
|
|
638
|
+
Prefix.objects.create(
|
|
639
|
+
prefix="192.0.4.0/32",
|
|
640
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
641
|
+
namespace=self.namespace,
|
|
642
|
+
status=self.status,
|
|
643
|
+
)
|
|
644
|
+
IPAddress.objects.create(
|
|
645
|
+
address="192.0.4.0/32",
|
|
646
|
+
status=ip_status,
|
|
647
|
+
namespace=self.namespace,
|
|
648
|
+
)
|
|
649
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
650
|
+
self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
|
|
651
|
+
|
|
652
|
+
data = {
|
|
653
|
+
"status": self.status.pk,
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
657
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
658
|
+
self.assertEqual(str(response.data["parent"]["url"]), self.absolute_api_url(prefix))
|
|
659
|
+
|
|
660
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
661
|
+
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
|
662
|
+
self.assertIn("detail", response.data)
|
|
663
|
+
|
|
597
664
|
def test_create_single_available_ip(self):
|
|
598
665
|
"""
|
|
599
|
-
Test
|
|
666
|
+
Test creating single IP will return 204 No content when pool is fully filled.
|
|
600
667
|
"""
|
|
601
668
|
prefix = Prefix.objects.create(
|
|
602
669
|
prefix="192.0.2.0/29",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from unittest import skipIf
|
|
2
|
+
from unittest.mock import patch
|
|
2
3
|
|
|
3
4
|
from django.contrib.contenttypes.models import ContentType
|
|
4
5
|
from django.core.exceptions import ValidationError
|
|
@@ -413,6 +414,39 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
413
414
|
self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
|
|
414
415
|
self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
|
|
415
416
|
|
|
417
|
+
def test_parent_exists_after_model_clean(self):
|
|
418
|
+
prefix = Prefix(
|
|
419
|
+
prefix="101.102.0.2/26", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
|
|
420
|
+
)
|
|
421
|
+
prefix.clean()
|
|
422
|
+
self.assertEqual(prefix.parent, self.child1)
|
|
423
|
+
|
|
424
|
+
def test_reparent_subnets_and_reparent_ips_call(self):
|
|
425
|
+
"""Assert reparent_subnets and reparent_ips are only called if there is an update to either of network, namespace or prefix_length"""
|
|
426
|
+
prefix_ip = "101.102.0.0/28"
|
|
427
|
+
with self.subTest("Assert reparent_subnets"):
|
|
428
|
+
with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
|
|
429
|
+
Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
|
|
430
|
+
mock_reparent_subnets.assert_called_once()
|
|
431
|
+
|
|
432
|
+
with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
|
|
433
|
+
prefix = Prefix.objects.get(prefix=prefix_ip)
|
|
434
|
+
prefix.description = "Sample Description"
|
|
435
|
+
prefix.save()
|
|
436
|
+
mock_reparent_subnets.assert_not_called()
|
|
437
|
+
prefix.delete()
|
|
438
|
+
|
|
439
|
+
with self.subTest("Assert reparent_ips"):
|
|
440
|
+
with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
|
|
441
|
+
Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
|
|
442
|
+
reparent_ips.assert_called_once()
|
|
443
|
+
|
|
444
|
+
with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
|
|
445
|
+
prefix = Prefix.objects.get(prefix=prefix_ip)
|
|
446
|
+
prefix.description = "Sample Description"
|
|
447
|
+
prefix.save()
|
|
448
|
+
reparent_ips.assert_not_called()
|
|
449
|
+
|
|
416
450
|
def test_location_queries(self):
|
|
417
451
|
locations = Location.objects.all()[:4]
|
|
418
452
|
for location in locations:
|
|
@@ -766,6 +800,13 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
766
800
|
IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
|
|
767
801
|
self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
|
|
768
802
|
|
|
803
|
+
def test_get_first_available_ip_calculate_child_ips(self):
|
|
804
|
+
parent_prefix = Prefix.objects.create(prefix="10.0.3.0/29", status=self.status, namespace=self.namespace)
|
|
805
|
+
Prefix.objects.create(prefix="10.0.3.0/30", status=self.status, namespace=self.namespace)
|
|
806
|
+
IPAddress(address="10.0.3.1/30", status=self.status, namespace=self.namespace).save()
|
|
807
|
+
|
|
808
|
+
self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
|
|
809
|
+
|
|
769
810
|
def test_get_utilization(self):
|
|
770
811
|
# Container Prefix
|
|
771
812
|
prefix = Prefix.objects.create(
|