nautobot 2.3.12__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/api/serializers.py +1 -0
- nautobot/core/celery/log.py +4 -4
- nautobot/core/filters.py +2 -0
- nautobot/core/jobs/cleanup.py +47 -11
- nautobot/core/models/tree_queries.py +5 -2
- nautobot/core/settings.py +1 -1
- nautobot/core/tables.py +60 -10
- nautobot/core/templates/search.html +7 -0
- nautobot/core/templatetags/helpers.py +7 -1
- nautobot/core/testing/api.py +5 -1
- nautobot/core/testing/filters.py +20 -5
- nautobot/core/tests/test_api.py +20 -0
- nautobot/core/tests/test_csv.py +25 -3
- nautobot/core/tests/test_utils.py +8 -0
- nautobot/core/utils/lookup.py +11 -8
- nautobot/dcim/api/views.py +3 -0
- nautobot/dcim/filters/__init__.py +26 -1
- nautobot/dcim/forms.py +10 -5
- nautobot/dcim/models/devices.py +1 -0
- nautobot/dcim/tests/test_filters.py +33 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/dcim/tests/test_views.py +6 -0
- nautobot/extras/api/serializers.py +1 -0
- nautobot/extras/api/views.py +2 -0
- nautobot/extras/forms/forms.py +2 -0
- nautobot/extras/forms/mixins.py +10 -2
- nautobot/extras/group_sync.py +3 -3
- nautobot/extras/jobs.py +6 -4
- nautobot/extras/plugins/__init__.py +13 -2
- nautobot/extras/tests/test_views.py +2 -0
- nautobot/ipam/lookups.py +101 -62
- nautobot/ipam/models.py +62 -11
- nautobot/ipam/tables.py +20 -6
- nautobot/ipam/tests/test_api.py +68 -1
- nautobot/ipam/tests/test_models.py +41 -0
- nautobot/ipam/tests/test_querysets.py +49 -1
- nautobot/ipam/utils/__init__.py +24 -0
- nautobot/ipam/views.py +61 -68
- 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 +197 -5
- 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 +16 -2
- 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 +23 -4
- 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/objects.inv +0 -0
- 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 +614 -179
- 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 +553 -200
- 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 +3 -3
- 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/users/api/serializers.py +1 -0
- nautobot/virtualization/filters.py +19 -2
- nautobot/virtualization/tests/test_filters.py +9 -0
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/METADATA +3 -3
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/RECORD +324 -324
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/NOTICE +0 -0
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/WHEEL +0 -0
- {nautobot-2.3.12.dist-info → nautobot-2.3.14.dist-info}/entry_points.txt +0 -0
nautobot/ipam/lookups.py
CHANGED
|
@@ -3,7 +3,9 @@ from django.db.models import Lookup, lookups
|
|
|
3
3
|
import netaddr
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def _mysql_varbin_to_broadcast():
|
|
6
|
+
def _mysql_varbin_to_broadcast(alias=None):
|
|
7
|
+
if alias:
|
|
8
|
+
return f"HEX({alias}.broadcast)"
|
|
7
9
|
return "HEX(broadcast)"
|
|
8
10
|
|
|
9
11
|
|
|
@@ -13,11 +15,15 @@ def _mysql_varbin_to_hex(lhs, alias=None):
|
|
|
13
15
|
return f"HEX({lhs})"
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
def _mysql_varbin_to_network():
|
|
18
|
+
def _mysql_varbin_to_network(alias=None):
|
|
19
|
+
if alias:
|
|
20
|
+
return f"HEX({alias}.network)"
|
|
17
21
|
return "HEX(network)"
|
|
18
22
|
|
|
19
23
|
|
|
20
|
-
def _postgresql_varbin_to_broadcast(length):
|
|
24
|
+
def _postgresql_varbin_to_broadcast(length, alias=None):
|
|
25
|
+
if alias:
|
|
26
|
+
return f"right({alias}.broadcast::text, -1)::varbit::bit({length})"
|
|
21
27
|
return f"right(broadcast::text, -1)::varbit::bit({length})"
|
|
22
28
|
|
|
23
29
|
|
|
@@ -27,8 +33,10 @@ def _postgresql_varbin_to_integer(lhs, length, alias=None):
|
|
|
27
33
|
return f"right({lhs}::text, -1)::varbit::bit({length})"
|
|
28
34
|
|
|
29
35
|
|
|
30
|
-
def _postgresql_varbin_to_network(lhs, length):
|
|
36
|
+
def _postgresql_varbin_to_network(lhs, length, alias=None):
|
|
31
37
|
# convert to bitstring, 0 out everything larger than prefix_length
|
|
38
|
+
if alias:
|
|
39
|
+
return f"lpad(right({alias}.{lhs}::text, -1)::varbit::text, {alias}.prefix_length, '0')::bit({length})"
|
|
32
40
|
return f"lpad(right({lhs}::text, -1)::varbit::text, prefix_length, '0')::bit({length})"
|
|
33
41
|
|
|
34
42
|
|
|
@@ -52,8 +60,8 @@ def get_ip_info(field_name, ip_str, alias=None):
|
|
|
52
60
|
ip_details.rhs = py_to_hex(ip.ip, ip_details.length)
|
|
53
61
|
ip_details.net_addr = f"'{py_to_hex(ip.network, ip_details.length)}'"
|
|
54
62
|
ip_details.bcast_addr = f"'{py_to_hex(ip[-1], ip_details.length)}'"
|
|
55
|
-
ip_details.q_net = _mysql_varbin_to_network()
|
|
56
|
-
ip_details.q_bcast = _mysql_varbin_to_broadcast()
|
|
63
|
+
ip_details.q_net = _mysql_varbin_to_network(alias=alias)
|
|
64
|
+
ip_details.q_bcast = _mysql_varbin_to_broadcast(alias=alias)
|
|
57
65
|
ip_details.q_ip = _mysql_varbin_to_hex(field_name, alias=alias)
|
|
58
66
|
|
|
59
67
|
elif _connection.vendor == "postgresql":
|
|
@@ -61,8 +69,8 @@ def get_ip_info(field_name, ip_str, alias=None):
|
|
|
61
69
|
ip_details.addr_str = f"B'{bin(int(ip_details.addr))[2:].zfill(ip_details.length)}'"
|
|
62
70
|
ip_details.net_addr = f"B'{bin(int(ip.network))[2:].zfill(ip_details.length)}'"
|
|
63
71
|
ip_details.bcast_addr = f"B'{bin(int(ip[-1]))[2:].zfill(ip_details.length)}'"
|
|
64
|
-
ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length)
|
|
65
|
-
ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length)
|
|
72
|
+
ip_details.q_net = _postgresql_varbin_to_network(field_name, ip_details.length, alias=alias)
|
|
73
|
+
ip_details.q_bcast = _postgresql_varbin_to_broadcast(ip_details.length, alias=alias)
|
|
66
74
|
ip_details.q_ip = _postgresql_varbin_to_integer(field_name, ip_details.length, alias=alias)
|
|
67
75
|
|
|
68
76
|
return ip_details
|
|
@@ -71,25 +79,38 @@ def get_ip_info(field_name, ip_str, alias=None):
|
|
|
71
79
|
class IPDetails:
|
|
72
80
|
"""Class for setting up all details about an IP they may be needed"""
|
|
73
81
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
q_ip = None
|
|
82
|
+
addr = None # 10.0.0.0
|
|
83
|
+
ip = None # 10.0.0.0/8
|
|
84
|
+
prefix = None # 8
|
|
85
|
+
length = None # 32
|
|
86
|
+
addr_str = None # B'00001010000000000000000000000000'
|
|
87
|
+
rhs = None # 00001010000000000000000000000000
|
|
88
|
+
net_addr = None # B'00001010000000000000000000000000'
|
|
89
|
+
bcast_addr = None # B'00001010111111111111111111111111'
|
|
90
|
+
q_net = None # mysql or postgres specific
|
|
91
|
+
q_bcast = None # mysql or postgres specific
|
|
92
|
+
q_ip = None # mysql or postgres specific
|
|
86
93
|
to_len = {4: 32, 6: 128}
|
|
87
94
|
|
|
95
|
+
def __str__(self):
|
|
96
|
+
return f"""\
|
|
97
|
+
addr: {self.addr}
|
|
98
|
+
ip: {self.ip}
|
|
99
|
+
prefix: {self.prefix}
|
|
100
|
+
length: {self.length}
|
|
101
|
+
addr_str: {self.addr_str}
|
|
102
|
+
rhs: {self.rhs}
|
|
103
|
+
net_addr: {self.net_addr}
|
|
104
|
+
bcast_addr: {self.bcast_addr}
|
|
105
|
+
q_net: {self.q_net}
|
|
106
|
+
q_bcast: {self.q_bcast}
|
|
107
|
+
q_ip: {self.q_ip}"""
|
|
108
|
+
|
|
88
109
|
|
|
89
110
|
class StringMatchMixin:
|
|
90
|
-
def process_lhs(self,
|
|
111
|
+
def process_lhs(self, compiler, connection, lhs=None):
|
|
91
112
|
lhs = lhs or self.lhs
|
|
92
|
-
lhs_string, lhs_params =
|
|
113
|
+
lhs_string, lhs_params = compiler.compile(lhs)
|
|
93
114
|
if connection.vendor == "postgresql":
|
|
94
115
|
raise NotSupportedError("Lookup not supported on postgresql.")
|
|
95
116
|
return f"INET6_NTOA({lhs_string})", lhs_params
|
|
@@ -129,6 +150,7 @@ class IRegex(StringMatchMixin, lookups.IRegex):
|
|
|
129
150
|
|
|
130
151
|
class NetworkFieldMixin:
|
|
131
152
|
def get_prep_lookup(self):
|
|
153
|
+
self.alias = self.lhs.alias
|
|
132
154
|
field_name = self.lhs.field.name
|
|
133
155
|
if field_name not in ["host", "network"]:
|
|
134
156
|
raise NotSupportedError(f"Lookup only provided on the host and network fields, not {field_name}.")
|
|
@@ -139,8 +161,8 @@ class NetworkFieldMixin:
|
|
|
139
161
|
self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
|
|
140
162
|
return str(self.ip.ip)
|
|
141
163
|
|
|
142
|
-
def process_rhs(self,
|
|
143
|
-
sql, params = super().process_rhs(
|
|
164
|
+
def process_rhs(self, compiler, connection):
|
|
165
|
+
sql, params = super().process_rhs(compiler, connection)
|
|
144
166
|
params[0] = self.ip.rhs
|
|
145
167
|
return sql, params
|
|
146
168
|
|
|
@@ -148,50 +170,67 @@ class NetworkFieldMixin:
|
|
|
148
170
|
class NetEquals(NetworkFieldMixin, Lookup):
|
|
149
171
|
lookup_name = "net_equals"
|
|
150
172
|
|
|
151
|
-
def as_sql(self,
|
|
152
|
-
_, lhs_params = self.process_lhs(
|
|
153
|
-
rhs, rhs_params = self.process_rhs(
|
|
154
|
-
|
|
173
|
+
def as_sql(self, compiler, connection):
|
|
174
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
175
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
176
|
+
if self.alias:
|
|
177
|
+
query = f"{self.alias}.prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
|
|
178
|
+
else:
|
|
179
|
+
query = f"prefix_length = {self.ip.prefix} AND {rhs} = {self.ip.q_ip}"
|
|
155
180
|
return query, lhs_params + rhs_params
|
|
156
181
|
|
|
157
182
|
|
|
158
183
|
class NetContainsOrEquals(NetworkFieldMixin, Lookup):
|
|
159
184
|
lookup_name = "net_contains_or_equals"
|
|
160
185
|
|
|
161
|
-
def as_sql(self,
|
|
162
|
-
_, lhs_params = self.process_lhs(
|
|
163
|
-
rhs, rhs_params = self.process_rhs(
|
|
164
|
-
|
|
186
|
+
def as_sql(self, compiler, connection):
|
|
187
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
188
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
189
|
+
if self.alias:
|
|
190
|
+
query = f"{self.alias}.prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
|
|
191
|
+
else:
|
|
192
|
+
query = f"prefix_length <= {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
|
|
165
193
|
return query, lhs_params + rhs_params
|
|
166
194
|
|
|
167
195
|
|
|
168
196
|
class NetContains(NetworkFieldMixin, Lookup):
|
|
169
197
|
lookup_name = "net_contains"
|
|
170
198
|
|
|
171
|
-
def as_sql(self,
|
|
172
|
-
_, lhs_params = self.process_lhs(
|
|
173
|
-
rhs, rhs_params = self.process_rhs(
|
|
174
|
-
|
|
199
|
+
def as_sql(self, compiler, connection):
|
|
200
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
201
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
202
|
+
if self.alias:
|
|
203
|
+
query = (
|
|
204
|
+
f"{self.alias}.prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
query = f"prefix_length < {self.ip.prefix} AND {rhs} BETWEEN {self.ip.q_net} AND {self.ip.q_bcast}"
|
|
175
208
|
return query, lhs_params + rhs_params
|
|
176
209
|
|
|
177
210
|
|
|
178
211
|
class NetContainedOrEqual(NetworkFieldMixin, Lookup):
|
|
179
212
|
lookup_name = "net_contained_or_equal"
|
|
180
213
|
|
|
181
|
-
def as_sql(self,
|
|
182
|
-
_, lhs_params = self.process_lhs(
|
|
183
|
-
rhs, rhs_params = self.process_rhs(
|
|
184
|
-
|
|
214
|
+
def as_sql(self, compiler, connection):
|
|
215
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
216
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
217
|
+
if self.alias:
|
|
218
|
+
query = f"{self.alias}.prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
|
|
219
|
+
else:
|
|
220
|
+
query = f"prefix_length >= {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
|
|
185
221
|
return query, lhs_params + rhs_params
|
|
186
222
|
|
|
187
223
|
|
|
188
224
|
class NetContained(NetworkFieldMixin, Lookup):
|
|
189
225
|
lookup_name = "net_contained"
|
|
190
226
|
|
|
191
|
-
def as_sql(self,
|
|
192
|
-
_, lhs_params = self.process_lhs(
|
|
193
|
-
rhs, rhs_params = self.process_rhs(
|
|
194
|
-
|
|
227
|
+
def as_sql(self, compiler, connection):
|
|
228
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
229
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
230
|
+
if self.alias:
|
|
231
|
+
query = f"{self.alias}.prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
|
|
232
|
+
else:
|
|
233
|
+
query = f"prefix_length > {self.ip.prefix} AND {self.ip.q_net} BETWEEN {rhs} AND {self.ip.bcast_addr}"
|
|
195
234
|
return query, lhs_params + rhs_params
|
|
196
235
|
|
|
197
236
|
|
|
@@ -205,19 +244,19 @@ class NetHost(Lookup):
|
|
|
205
244
|
self.ip = get_ip_info(field_name, self.rhs, alias=self.lhs.alias)
|
|
206
245
|
return str(self.ip.ip)
|
|
207
246
|
|
|
208
|
-
def process_rhs(self,
|
|
209
|
-
sql, params = super().process_rhs(
|
|
247
|
+
def process_rhs(self, compiler, connection):
|
|
248
|
+
sql, params = super().process_rhs(compiler, connection)
|
|
210
249
|
params[0] = self.ip.rhs
|
|
211
250
|
return sql, params
|
|
212
251
|
|
|
213
|
-
def process_lhs(self,
|
|
252
|
+
def process_lhs(self, compiler, connection, lhs=None):
|
|
214
253
|
lhs = lhs or self.lhs
|
|
215
|
-
_, lhs_params =
|
|
254
|
+
_, lhs_params = compiler.compile(lhs)
|
|
216
255
|
return self.ip.q_ip, lhs_params
|
|
217
256
|
|
|
218
|
-
def as_sql(self,
|
|
219
|
-
lhs, lhs_params = self.process_lhs(
|
|
220
|
-
rhs, rhs_params = self.process_rhs(
|
|
257
|
+
def as_sql(self, compiler, connection):
|
|
258
|
+
lhs, lhs_params = self.process_lhs(compiler, connection)
|
|
259
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
221
260
|
return f"{lhs} = {rhs}", lhs_params + rhs_params
|
|
222
261
|
|
|
223
262
|
|
|
@@ -242,9 +281,9 @@ class NetIn(Lookup):
|
|
|
242
281
|
self.query_starter = "'1' != ANY(%s) AND "
|
|
243
282
|
return self.rhs
|
|
244
283
|
|
|
245
|
-
def as_sql(self,
|
|
246
|
-
_, lhs_params = self.process_lhs(
|
|
247
|
-
_, rhs_params = self.process_rhs(
|
|
284
|
+
def as_sql(self, compiler, connection):
|
|
285
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
286
|
+
_, rhs_params = self.process_rhs(compiler, connection)
|
|
248
287
|
query = self.query_starter
|
|
249
288
|
query += "OR ".join(f"{ip.q_ip} BETWEEN {ip.net_addr} AND {ip.bcast_addr} " for ip in self.ips)
|
|
250
289
|
return query, lhs_params + rhs_params
|
|
@@ -253,9 +292,9 @@ class NetIn(Lookup):
|
|
|
253
292
|
class NetHostContained(NetworkFieldMixin, Lookup):
|
|
254
293
|
lookup_name = "net_host_contained"
|
|
255
294
|
|
|
256
|
-
def as_sql(self,
|
|
257
|
-
_, lhs_params = self.process_lhs(
|
|
258
|
-
rhs, rhs_params = self.process_rhs(
|
|
295
|
+
def as_sql(self, compiler, connection):
|
|
296
|
+
_, lhs_params = self.process_lhs(compiler, connection)
|
|
297
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
259
298
|
query = f"{self.ip.q_ip} BETWEEN {rhs} AND {self.ip.bcast_addr}"
|
|
260
299
|
return query, lhs_params + rhs_params
|
|
261
300
|
|
|
@@ -270,12 +309,12 @@ class NetFamily(Lookup):
|
|
|
270
309
|
self.rhs = 16
|
|
271
310
|
return self.rhs
|
|
272
311
|
|
|
273
|
-
def process_lhs(self,
|
|
312
|
+
def process_lhs(self, compiler, connection, lhs=None):
|
|
274
313
|
lhs = lhs or self.lhs
|
|
275
|
-
lhs_string, lhs_params =
|
|
314
|
+
lhs_string, lhs_params = compiler.compile(lhs)
|
|
276
315
|
return f"LENGTH({lhs_string})", lhs_params
|
|
277
316
|
|
|
278
|
-
def as_sql(self,
|
|
279
|
-
lhs, lhs_params = self.process_lhs(
|
|
280
|
-
rhs, rhs_params = self.process_rhs(
|
|
317
|
+
def as_sql(self, compiler, connection):
|
|
318
|
+
lhs, lhs_params = self.process_lhs(compiler, connection)
|
|
319
|
+
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
281
320
|
return f"{lhs} = {rhs}", lhs_params + rhs_params
|
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",
|
|
@@ -442,13 +442,27 @@ class IPAddressTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
442
442
|
)
|
|
443
443
|
tenant = TenantColumn()
|
|
444
444
|
parent__namespace = tables.Column(linkify=True)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
445
|
+
interface_count = LinkedCountColumn(
|
|
446
|
+
viewname="dcim:interface_list", url_params={"ip_addresses": "pk"}, verbose_name="Interfaces"
|
|
447
|
+
)
|
|
448
|
+
# TODO: what about interfaces assigned to modules?
|
|
449
|
+
interface_parent_count = LinkedCountColumn(
|
|
450
|
+
viewname="dcim:device_list",
|
|
451
|
+
url_params={"ip_addresses": "pk"},
|
|
452
|
+
reverse_lookup="interfaces__ip_addresses",
|
|
453
|
+
distinct=True,
|
|
454
|
+
verbose_name="Devices",
|
|
455
|
+
)
|
|
448
456
|
vm_interface_count = LinkedCountColumn(
|
|
449
457
|
viewname="virtualization:vminterface_list", url_params={"ip_addresses": "pk"}, verbose_name="VM Interfaces"
|
|
450
458
|
)
|
|
451
|
-
vm_interface_parent_count =
|
|
459
|
+
vm_interface_parent_count = LinkedCountColumn(
|
|
460
|
+
viewname="virtualization:virtualmachine_list",
|
|
461
|
+
url_params={"ip_addresses": "pk"},
|
|
462
|
+
reverse_lookup="interfaces__ip_addresses",
|
|
463
|
+
distinct=True,
|
|
464
|
+
verbose_name="Virtual Machines",
|
|
465
|
+
)
|
|
452
466
|
|
|
453
467
|
class Meta(BaseTable.Meta):
|
|
454
468
|
model = IPAddress
|
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(
|