nautobot 2.4.15__py3-none-any.whl → 2.4.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/utils.py +2 -0
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
- nautobot/cloud/views.py +7 -0
- nautobot/core/apps/__init__.py +1 -0
- nautobot/core/celery/__init__.py +2 -1
- nautobot/core/templates/components/panel/panel.html +1 -1
- nautobot/core/templates/inc/paginator.html +3 -3
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templatetags/helpers.py +80 -6
- nautobot/core/testing/mixins.py +1 -1
- nautobot/core/testing/views.py +2 -4
- nautobot/core/ui/bulk_buttons.py +53 -53
- nautobot/core/ui/object_detail.py +9 -4
- nautobot/core/utils/data.py +13 -0
- nautobot/core/utils/deprecation.py +2 -0
- nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
- nautobot/dcim/models/device_component_templates.py +4 -2
- nautobot/dcim/models/device_components.py +3 -2
- nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
- nautobot/dcim/views.py +9 -0
- nautobot/extras/models/customfields.py +45 -9
- nautobot/extras/tables.py +12 -0
- nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
- nautobot/extras/templates/extras/configcontext_update.html +49 -49
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
- nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
- nautobot/extras/templates/extras/inc/job_table.html +1 -1
- nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
- nautobot/extras/templates/extras/marketplace.html +1 -1
- nautobot/extras/templates/extras/note_retrieve.html +53 -53
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +2 -29
- nautobot/extras/templates/extras/tag_retrieve.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +14 -14
- nautobot/extras/templates/extras/team_retrieve.html +1 -1
- nautobot/extras/tests/test_models.py +216 -0
- nautobot/extras/tests/test_plugins.py +12 -0
- nautobot/extras/tests/test_views.py +2 -2
- nautobot/extras/views.py +20 -4
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/jobs/__init__.py +10 -0
- nautobot/ipam/jobs/cleanup.py +296 -0
- nautobot/ipam/models.py +301 -178
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
- nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
- nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +1 -1
- nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
- nautobot/ipam/tests/test_jobs.py +454 -0
- nautobot/ipam/tests/test_models.py +290 -122
- nautobot/ipam/tests/test_views.py +40 -164
- nautobot/ipam/urls.py +0 -11
- nautobot/ipam/utils/testing.py +9 -4
- nautobot/ipam/views.py +166 -235
- nautobot/project-static/docs/404.html +9 -6
- nautobot/project-static/docs/apps/index.html +9 -6
- nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
- nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +28 -9
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +9 -6
- nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
- nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
- nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
- nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +11 -8
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
- nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
- nautobot/project-static/docs/development/apps/api/setup.html +9 -6
- nautobot/project-static/docs/development/apps/api/testing.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
- nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
- nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
- nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
- nautobot/project-static/docs/development/apps/index.html +9 -6
- nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
- nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
- nautobot/project-static/docs/development/core/application-registry.html +23 -20
- nautobot/project-static/docs/development/core/best-practices.html +23 -20
- nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
- nautobot/project-static/docs/development/core/caching.html +9 -6
- nautobot/project-static/docs/development/core/controllers.html +9 -6
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
- nautobot/project-static/docs/development/core/generic-views.html +9 -6
- nautobot/project-static/docs/development/core/getting-started.html +9 -6
- nautobot/project-static/docs/development/core/homepage.html +12 -9
- nautobot/project-static/docs/development/core/index.html +9 -6
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
- nautobot/project-static/docs/development/core/model-checklist.html +9 -6
- nautobot/project-static/docs/development/core/model-features.html +11 -8
- nautobot/project-static/docs/development/core/natural-keys.html +21 -18
- nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
- nautobot/project-static/docs/development/core/release-checklist.html +9 -6
- nautobot/project-static/docs/development/core/role-internals.html +9 -6
- nautobot/project-static/docs/development/core/settings.html +9 -6
- nautobot/project-static/docs/development/core/style-guide.html +32 -29
- nautobot/project-static/docs/development/core/templates.html +9 -6
- nautobot/project-static/docs/development/core/testing.html +10 -7
- nautobot/project-static/docs/development/core/ui-component-framework.html +36 -33
- nautobot/project-static/docs/development/core/user-preferences.html +9 -6
- nautobot/project-static/docs/development/index.html +9 -6
- nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
- nautobot/project-static/docs/development/jobs/index.html +9 -6
- nautobot/project-static/docs/development/jobs/installation.html +23 -20
- nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
- nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
- nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
- nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
- nautobot/project-static/docs/development/jobs/testing.html +14 -11
- nautobot/project-static/docs/index.html +9 -6
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +9 -6
- nautobot/project-static/docs/overview/design_philosophy.html +9 -6
- nautobot/project-static/docs/release-notes/index.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
- nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
- nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.4.html +342 -6
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +301 -301
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +11 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
- nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
- nautobot/project-static/docs/user-guide/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
- nautobot/project-static/fonts/UFL.txt +96 -96
- nautobot/project-static/js/forms.js +35 -2
- nautobot/virtualization/filters.py +7 -0
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/METADATA +6 -6
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/RECORD +373 -368
- nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/NOTICE +0 -0
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/WHEEL +0 -0
- {nautobot-2.4.15.dist-info → nautobot-2.4.17.dist-info}/entry_points.txt +0 -0
nautobot/ipam/models.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import operator
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from django.contrib.contenttypes.models import ContentType
|
|
5
6
|
from django.core.exceptions import MultipleObjectsReturned, ValidationError
|
|
@@ -602,44 +603,40 @@ class Prefix(PrimaryModel):
|
|
|
602
603
|
unique_together = ["namespace", "network", "prefix_length"]
|
|
603
604
|
verbose_name_plural = "prefixes"
|
|
604
605
|
|
|
605
|
-
def validate_unique(self, exclude=None):
|
|
606
|
-
if self.namespace is None:
|
|
607
|
-
if Prefix.objects.filter(
|
|
608
|
-
network=self.network, prefix_length=self.prefix_length, namespace__isnull=True
|
|
609
|
-
).exists():
|
|
610
|
-
raise ValidationError(
|
|
611
|
-
{"__all__": "Prefix with this Namespace, Network and Prefix length already exists."}
|
|
612
|
-
)
|
|
613
|
-
super().validate_unique(exclude)
|
|
614
|
-
|
|
615
606
|
def __init__(self, *args, **kwargs):
|
|
616
607
|
prefix = kwargs.pop("prefix", None)
|
|
617
608
|
self._location = kwargs.pop("location", None)
|
|
618
609
|
super().__init__(*args, **kwargs)
|
|
619
610
|
|
|
620
611
|
# Initialize cached fields
|
|
621
|
-
self.
|
|
612
|
+
self._parent_id = None
|
|
622
613
|
self._network = None
|
|
614
|
+
self._broadcast = None
|
|
623
615
|
self._prefix_length = None
|
|
624
616
|
self._namespace_id = None
|
|
617
|
+
self._ip_version = None
|
|
618
|
+
self._cleaned = False
|
|
625
619
|
|
|
626
620
|
self._deconstruct_prefix(prefix)
|
|
627
621
|
|
|
628
|
-
@staticmethod
|
|
629
|
-
def _extract_field_value(field_name, field_names, values):
|
|
630
|
-
for current_field, field_value in zip(field_names, values):
|
|
631
|
-
if field_name == current_field:
|
|
632
|
-
return field_value
|
|
633
|
-
return None
|
|
634
|
-
|
|
635
622
|
@classmethod
|
|
636
623
|
def from_db(cls, db, field_names, values):
|
|
637
624
|
instance = super().from_db(db, field_names, values)
|
|
638
625
|
# These cached values are used to detect changes during save,
|
|
639
626
|
# avoiding unnecessary re-parenting of subnets and IPs if these fields have not been updated.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
627
|
+
for field_name, value in zip(field_names, values):
|
|
628
|
+
if field_name == "broadcast":
|
|
629
|
+
instance._broadcast = value
|
|
630
|
+
elif field_name == "ip_version":
|
|
631
|
+
instance._ip_version = value
|
|
632
|
+
elif field_name == "namespace_id":
|
|
633
|
+
instance._namespace_id = value
|
|
634
|
+
elif field_name == "network":
|
|
635
|
+
instance._network = value
|
|
636
|
+
elif field_name == "parent_id":
|
|
637
|
+
instance._parent_id = value
|
|
638
|
+
elif field_name == "prefix_length":
|
|
639
|
+
instance._prefix_length = value
|
|
643
640
|
return instance
|
|
644
641
|
|
|
645
642
|
def __str__(self):
|
|
@@ -671,116 +668,128 @@ class Prefix(PrimaryModel):
|
|
|
671
668
|
|
|
672
669
|
def delete(self, *args, **kwargs):
|
|
673
670
|
"""
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
If a Prefix has children, this catches the error and explicitly updates the
|
|
677
|
-
`protected_objects` from the exception setting their parent to the old parent of this
|
|
678
|
-
prefix, and then this prefix will be deleted.
|
|
671
|
+
As a part of deleting this Prefix, reparent any child Prefixes or IPAddresses to this Prefix's parent if any.
|
|
679
672
|
"""
|
|
680
673
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
# ):
|
|
694
|
-
if protected_model == IPAddress and self.parent is None:
|
|
695
|
-
raise models.ProtectedError(
|
|
696
|
-
msg=(
|
|
697
|
-
f"Cannot delete Prefix {self} because it has child IPAddress objects that "
|
|
698
|
-
"would no longer have a valid parent."
|
|
699
|
-
),
|
|
700
|
-
protected_objects=err.protected_objects,
|
|
701
|
-
) from err
|
|
702
|
-
|
|
703
|
-
elif protected_model not in (IPAddress, Prefix):
|
|
704
|
-
raise
|
|
705
|
-
# 3.0 TODO: uncomment this check to enforce it
|
|
706
|
-
# Prefix objects must have a valid parent
|
|
707
|
-
# elif (
|
|
708
|
-
# protected_model == Prefix
|
|
709
|
-
# and self.parent is not None
|
|
710
|
-
# and constants.PREFIX_ALLOWED_PARENT_TYPES[instance.type] != self.parent.type
|
|
711
|
-
# ):
|
|
712
|
-
# raise models.ProtectedError(
|
|
713
|
-
# msg=(
|
|
714
|
-
# f"Cannot delete Prefix {self} because it has child Prefix objects that "
|
|
715
|
-
# "would no longer have a valid parent."
|
|
716
|
-
# ),
|
|
717
|
-
# protected_objects=err.protected_objects,
|
|
718
|
-
# ) from err
|
|
719
|
-
|
|
720
|
-
# Update protected objects to use the new parent and delete the old parent (self).
|
|
721
|
-
protected_pks = (po.pk for po in err.protected_objects)
|
|
722
|
-
protected_objects = protected_model.objects.filter(pk__in=protected_pks)
|
|
723
|
-
protected_objects.update(parent=self.parent)
|
|
674
|
+
with transaction.atomic():
|
|
675
|
+
if self.parent is None and self.ip_addresses.exists():
|
|
676
|
+
raise models.ProtectedError(
|
|
677
|
+
msg=(
|
|
678
|
+
f"Cannot delete Prefix {self} because it has child IPAddress objects that "
|
|
679
|
+
"would no longer have a valid parent."
|
|
680
|
+
),
|
|
681
|
+
protected_objects=self.ip_addresses.all(),
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
self.children.update(parent=self.parent)
|
|
685
|
+
self.ip_addresses.update(parent=self.parent)
|
|
724
686
|
return super().delete(*args, **kwargs)
|
|
725
687
|
|
|
726
688
|
def get_parent(self):
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return self._parent
|
|
689
|
+
"""
|
|
690
|
+
Identify the prefix in this namespace that should serve as the parent of this Prefix.
|
|
730
691
|
|
|
692
|
+
Note that for historical reasons this does not directly set `self.parent`, but just returns the candidate.
|
|
693
|
+
"""
|
|
694
|
+
# Closest ancestor by `prefix_length`.
|
|
731
695
|
if supernets := self.supernets():
|
|
732
|
-
|
|
733
|
-
self._parent = parent
|
|
734
|
-
return parent
|
|
696
|
+
return max(supernets, key=operator.attrgetter("prefix_length"))
|
|
735
697
|
return None
|
|
736
698
|
|
|
737
|
-
|
|
699
|
+
@property
|
|
700
|
+
def _networking_values_changed(self) -> bool:
|
|
701
|
+
"""
|
|
702
|
+
Check if the networking fields of this Prefix have changed compared to their database values.
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
bool: True if any of (network, broadcast, prefix_length, ip_version) have changed.
|
|
706
|
+
"""
|
|
707
|
+
if not self.present_in_database:
|
|
708
|
+
return False
|
|
709
|
+
return (
|
|
710
|
+
self._network != self.network
|
|
711
|
+
or self._broadcast != self.broadcast
|
|
712
|
+
or self._prefix_length != self.prefix_length
|
|
713
|
+
or self._ip_version != self.ip_version
|
|
714
|
+
)
|
|
738
715
|
|
|
739
716
|
def clean(self):
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
717
|
+
"""
|
|
718
|
+
Perform various data sanitization and validation.
|
|
719
|
+
|
|
720
|
+
- Prevent simultaneously changing network fields and namespace (too complex to handle)
|
|
721
|
+
- Ensure that `network`/`broadcast`/`prefix_length`/`ip_version` are set and self-consistent.
|
|
722
|
+
This includes clearing any host bits from the `prefix` or `network` based on the `prefix_length`.
|
|
723
|
+
- (Re)calculate `self.parent` if any networking fields have changed or if this is a new record.
|
|
724
|
+
- Raise a `ValidationError` if changes would orphan any existing child IPAddresses.
|
|
725
|
+
"""
|
|
726
|
+
if self.prefix is not None: # skip if missing a network/prefix_length; that will be caught by super().clean()
|
|
727
|
+
self._deconstruct_prefix(self.prefix)
|
|
745
728
|
|
|
746
|
-
|
|
729
|
+
# Determine correct `parent` from `namespace`/`prefix` if needed
|
|
730
|
+
if (
|
|
731
|
+
self._networking_values_changed
|
|
732
|
+
or self._namespace_id != self.namespace_id
|
|
733
|
+
or not self.present_in_database
|
|
734
|
+
):
|
|
735
|
+
self.parent = self.get_parent()
|
|
747
736
|
|
|
748
737
|
super().clean()
|
|
749
738
|
|
|
739
|
+
if self._networking_values_changed and self._namespace_id != self.namespace_id:
|
|
740
|
+
raise ValidationError(
|
|
741
|
+
{
|
|
742
|
+
"__all__": "Cannot change network and namespace in the same update. "
|
|
743
|
+
"Consider creating a new Prefix instead."
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
if self._networking_values_changed and self._parent_id is None:
|
|
748
|
+
orphaned_ips = self.ip_addresses.exclude(
|
|
749
|
+
ip_version=self.ip_version,
|
|
750
|
+
host__gte=self.network,
|
|
751
|
+
host__lte=self.broadcast,
|
|
752
|
+
)
|
|
753
|
+
if orphaned_ips_count := orphaned_ips.count():
|
|
754
|
+
raise ValidationError(
|
|
755
|
+
{
|
|
756
|
+
"__all__": f"{orphaned_ips_count} existing IP addresses (including "
|
|
757
|
+
f"{orphaned_ips.first().host}) would no longer have a valid parent Prefix after this change."
|
|
758
|
+
}
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
if self.present_in_database and self._namespace_id != self.namespace_id:
|
|
762
|
+
if self.vrfs.exists():
|
|
763
|
+
raise ValidationError(
|
|
764
|
+
{
|
|
765
|
+
"namespace": "Cannot move to a different Namespace while associated to VRFs in the current "
|
|
766
|
+
"Namespace. Remove all VRFs from this Prefix and descendants before making this change."
|
|
767
|
+
}
|
|
768
|
+
)
|
|
769
|
+
if VRFPrefixAssignment.objects.filter(
|
|
770
|
+
prefix__ip_version=self.ip_version,
|
|
771
|
+
prefix__network__gte=self.network,
|
|
772
|
+
prefix__broadcast__lte=self.broadcast,
|
|
773
|
+
prefix__prefix_length__gt=self.prefix_length,
|
|
774
|
+
prefix__namespace_id=self._namespace_id,
|
|
775
|
+
).exists():
|
|
776
|
+
raise ValidationError(
|
|
777
|
+
{
|
|
778
|
+
"namespace": "Cannot move to a different Namespace with descendant Prefixes associated to VRFs "
|
|
779
|
+
"in the current Namespace. Remove all VRFs from all descendants before making this change."
|
|
780
|
+
}
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
self._cleaned = True
|
|
784
|
+
|
|
750
785
|
clean.alters_data = True
|
|
751
786
|
|
|
752
787
|
def save(self, *args, **kwargs):
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
# err_msg = f"{self.type.title()} prefixes cannot be children of {self.parent.type.title()} prefixes"
|
|
759
|
-
# raise ValidationError({"type": err_msg})
|
|
760
|
-
|
|
761
|
-
# This is filtering on prefixes that share my parent and will be reparented to me
|
|
762
|
-
# but are not the correct type for this parent/child relationship
|
|
763
|
-
# 3.0 TODO: uncomment the below to enforce this constraint
|
|
764
|
-
# invalid_children = Prefix.objects.filter(
|
|
765
|
-
# ~models.Q(id=self.id),
|
|
766
|
-
# ~models.Q(type__in=constants.PREFIX_ALLOWED_CHILD_TYPES[self.type]),
|
|
767
|
-
# parent_id=self.parent_id,
|
|
768
|
-
# prefix_length__gt=self.prefix_length,
|
|
769
|
-
# ip_version=self.ip_version,
|
|
770
|
-
# network__gte=self.network,
|
|
771
|
-
# broadcast__lte=self.broadcast,
|
|
772
|
-
# namespace=self.namespace,
|
|
773
|
-
# )
|
|
774
|
-
#
|
|
775
|
-
# if invalid_children.exists():
|
|
776
|
-
# invalid_child_prefixes = [
|
|
777
|
-
# f"{child.cidr_str} ({child.type})" for child in invalid_children.only("network", "prefix_length")
|
|
778
|
-
# ]
|
|
779
|
-
# err_msg = (
|
|
780
|
-
# f'Creating prefix "{self.prefix}" in namespace "{self.namespace}" with type "{self.type}" '
|
|
781
|
-
# f"would create an invalid parent/child relationship with prefixes {invalid_child_prefixes}"
|
|
782
|
-
# )
|
|
783
|
-
# raise ValidationError({"__all__": err_msg})
|
|
788
|
+
"""
|
|
789
|
+
If not already cleaned, clean automatically before saving, and after saving, update related IPs and Prefixes.
|
|
790
|
+
"""
|
|
791
|
+
if not self._cleaned:
|
|
792
|
+
self.clean()
|
|
784
793
|
|
|
785
794
|
# cache the value of present_in_database; because after `super().save()`
|
|
786
795
|
# `self.present_in_database` would always return True`
|
|
@@ -788,29 +797,66 @@ class Prefix(PrimaryModel):
|
|
|
788
797
|
|
|
789
798
|
with transaction.atomic():
|
|
790
799
|
super().save(*args, **kwargs)
|
|
800
|
+
|
|
801
|
+
# Backward-compatibility: if this was initialized with a `location`, update the `locations` M2M now.
|
|
791
802
|
if self._location is not None:
|
|
792
803
|
self.location = self._location
|
|
793
804
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
+
if self._namespace_id != self.namespace_id and present_in_database:
|
|
806
|
+
# Namespace changed, move *all* descendants to the new namespace as well.
|
|
807
|
+
subnets = Prefix.objects.filter(
|
|
808
|
+
ip_version=self.ip_version,
|
|
809
|
+
network__gte=self.network,
|
|
810
|
+
broadcast__lte=self.broadcast,
|
|
811
|
+
prefix_length__gt=self.prefix_length,
|
|
812
|
+
namespace_id=self._namespace_id, # important!
|
|
813
|
+
)
|
|
814
|
+
# Is this a "merge" case where there are existing prefixes in the target namespace?
|
|
815
|
+
if (
|
|
816
|
+
Prefix.objects.filter(
|
|
817
|
+
ip_version=self.ip_version,
|
|
818
|
+
network__gte=self.network,
|
|
819
|
+
broadcast__lte=self.broadcast,
|
|
820
|
+
prefix_length__gt=self.prefix_length,
|
|
821
|
+
namespace_id=self.namespace_id, # important!
|
|
822
|
+
).exists()
|
|
823
|
+
or IPAddress.objects.filter(
|
|
824
|
+
ip_version=self.ip_version,
|
|
825
|
+
host__gte=self.network,
|
|
826
|
+
host__lte=self.broadcast,
|
|
827
|
+
parent__namespace_id=self.namespace_id,
|
|
828
|
+
).exists()
|
|
829
|
+
):
|
|
830
|
+
for subnet in subnets.iterator():
|
|
831
|
+
# We don't use `subnets.update()` here because we need to have each subnet call its own
|
|
832
|
+
# reparent_ips() and reparent_subnets()
|
|
833
|
+
subnet.namespace_id = self.namespace_id
|
|
834
|
+
subnet.save()
|
|
835
|
+
else:
|
|
836
|
+
# Nothing to merge, much more efficient:
|
|
837
|
+
subnets.update(namespace_id=self.namespace_id)
|
|
838
|
+
|
|
839
|
+
if self._networking_values_changed or self._namespace_id != self.namespace_id or not present_in_database:
|
|
840
|
+
# Claim and/or un-claim child prefixes and IPs
|
|
841
|
+
self.reparent_subnets()
|
|
842
|
+
self.reparent_ips()
|
|
843
|
+
|
|
844
|
+
self._network = self.network
|
|
845
|
+
self._broadcast = self.broadcast
|
|
846
|
+
self._prefix_length = self.prefix_length
|
|
847
|
+
self._ip_version = self.ip_version
|
|
848
|
+
self._namespace_id = self.namespace_id
|
|
849
|
+
self._parent_id = self.parent_id
|
|
850
|
+
self._cleaned = False
|
|
805
851
|
|
|
806
852
|
@property
|
|
807
|
-
def cidr_str(self):
|
|
853
|
+
def cidr_str(self) -> Optional[str]:
|
|
808
854
|
if self.network is not None and self.prefix_length is not None:
|
|
809
855
|
return f"{self.network}/{self.prefix_length}"
|
|
810
856
|
return None
|
|
811
857
|
|
|
812
858
|
@property
|
|
813
|
-
def prefix(self):
|
|
859
|
+
def prefix(self) -> Optional[netaddr.IPNetwork]:
|
|
814
860
|
if self.cidr_str:
|
|
815
861
|
return netaddr.IPNetwork(self.cidr_str)
|
|
816
862
|
return None
|
|
@@ -818,6 +864,7 @@ class Prefix(PrimaryModel):
|
|
|
818
864
|
@prefix.setter
|
|
819
865
|
def prefix(self, prefix):
|
|
820
866
|
self._deconstruct_prefix(prefix)
|
|
867
|
+
self._cleaned = False
|
|
821
868
|
|
|
822
869
|
@property
|
|
823
870
|
def location(self):
|
|
@@ -837,35 +884,87 @@ class Prefix(PrimaryModel):
|
|
|
837
884
|
|
|
838
885
|
def reparent_subnets(self):
|
|
839
886
|
"""
|
|
840
|
-
|
|
887
|
+
Handle changes to the parentage of other Prefixes as a consequence of this Prefix's creation or update.
|
|
841
888
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
query = Prefix.objects.select_for_update().filter(
|
|
846
|
-
~models.Q(id=self.id), # Don't include yourself...
|
|
847
|
-
parent_id=self.parent_id,
|
|
848
|
-
prefix_length__gt=self.prefix_length,
|
|
849
|
-
ip_version=self.ip_version,
|
|
850
|
-
network__gte=self.network,
|
|
851
|
-
broadcast__lte=self.broadcast,
|
|
852
|
-
namespace=self.namespace,
|
|
853
|
-
)
|
|
889
|
+
- Former child Prefixes of ours that are no longer our descendants can be reparented to our former parent.
|
|
890
|
+
- Subnets that we are now the closest parent of can be reparented to us.
|
|
891
|
+
- Former children that we are no longer the closest parent of can be reparented to one of our new descendants.
|
|
854
892
|
|
|
855
|
-
|
|
893
|
+
Called automatically by save(); generally not intended for use outside of that context.
|
|
894
|
+
"""
|
|
895
|
+
if self._networking_values_changed:
|
|
896
|
+
# Former child Prefixes of ours that are no longer our descendants can be reparented to our former parent.
|
|
897
|
+
self.children.exclude(
|
|
898
|
+
ip_version=self.ip_version,
|
|
899
|
+
network__gte=self.network,
|
|
900
|
+
broadcast__lte=self.broadcast,
|
|
901
|
+
prefix_length__gt=self.prefix_length,
|
|
902
|
+
namespace_id=self.namespace_id,
|
|
903
|
+
).update(parent_id=self._parent_id)
|
|
904
|
+
|
|
905
|
+
# Subnets that we are now the closest parent of can be reparented to us.
|
|
906
|
+
self.subnets().filter(
|
|
907
|
+
models.Q(parent__isnull=True) # No parent
|
|
908
|
+
| models.Q(parent__prefix_length__lt=self.prefix_length) # We're closer than the current parent
|
|
909
|
+
).update(parent=self)
|
|
910
|
+
|
|
911
|
+
if self._networking_values_changed:
|
|
912
|
+
# Former children that we are no longer the closest parent can be reparented to one of our new descendants.
|
|
913
|
+
children_to_reparent = []
|
|
914
|
+
for child in self.children.filter(prefix_length__gt=self.prefix_length + 1):
|
|
915
|
+
try:
|
|
916
|
+
closest_parent = self.children.get_closest_parent(child.prefix) # pylint: disable=no-member
|
|
917
|
+
except Prefix.DoesNotExist:
|
|
918
|
+
closest_parent = self
|
|
919
|
+
if closest_parent != self:
|
|
920
|
+
child.parent = closest_parent
|
|
921
|
+
children_to_reparent.append(child)
|
|
922
|
+
|
|
923
|
+
Prefix.objects.bulk_update(children_to_reparent, ["parent"], batch_size=1000)
|
|
856
924
|
|
|
857
925
|
reparent_subnets.alters_data = True
|
|
858
926
|
|
|
859
927
|
def reparent_ips(self):
|
|
860
|
-
"""
|
|
861
|
-
|
|
862
|
-
ip_version=self.ip_version,
|
|
863
|
-
parent_id=self.parent_id,
|
|
864
|
-
host__gte=self.network,
|
|
865
|
-
host__lte=self.broadcast,
|
|
866
|
-
)
|
|
928
|
+
"""
|
|
929
|
+
Handle changes to the parentage of IPAddresses as a consequence of this Prefix's creation or update.
|
|
867
930
|
|
|
868
|
-
|
|
931
|
+
- Former child IPs of ours that are no longer our descendants can be reparented to our former parent.
|
|
932
|
+
- Former child IPs that we are no longer the closest parent of can be reparented to one of our new descendants.
|
|
933
|
+
- IPs that we are now the closest parent of can be reparented to us.
|
|
934
|
+
|
|
935
|
+
Called automatically by save(); generally not intended for use outside of that context.
|
|
936
|
+
"""
|
|
937
|
+
if self._networking_values_changed:
|
|
938
|
+
# Former child IPs of ours that are no longer our descendants can be reparented to our former parent.
|
|
939
|
+
reparentable_ips = self.ip_addresses.exclude(
|
|
940
|
+
ip_version=self.ip_version,
|
|
941
|
+
host__gte=self.network,
|
|
942
|
+
host__lte=self.broadcast,
|
|
943
|
+
)
|
|
944
|
+
if self._parent_id is None and reparentable_ips.exists():
|
|
945
|
+
raise ValidationError(
|
|
946
|
+
{
|
|
947
|
+
"__all__": f"{reparentable_ips.count()} existing IP addresses would no longer have "
|
|
948
|
+
"a valid parent Prefix after this change."
|
|
949
|
+
}
|
|
950
|
+
)
|
|
951
|
+
reparentable_ips.update(parent_id=self._parent_id)
|
|
952
|
+
|
|
953
|
+
# Former child IPs that we are no longer closest parent of can be reparented to one of our new descendants.
|
|
954
|
+
ips_to_reparent = []
|
|
955
|
+
for ip in self.ip_addresses.all():
|
|
956
|
+
try:
|
|
957
|
+
closest_parent = self.children.get_closest_parent(ip.host, include_self=True) # pylint: disable=no-member
|
|
958
|
+
except Prefix.DoesNotExist:
|
|
959
|
+
closest_parent = self
|
|
960
|
+
if closest_parent != self:
|
|
961
|
+
ip.parent = closest_parent
|
|
962
|
+
ips_to_reparent.append(ip)
|
|
963
|
+
|
|
964
|
+
IPAddress.objects.bulk_update(ips_to_reparent, ["parent"], batch_size=1000)
|
|
965
|
+
|
|
966
|
+
# IPs that we are now the closest parent of can be reparented to us.
|
|
967
|
+
self.get_all_ips().select_for_update().filter(parent__prefix_length__lt=self.prefix_length).update(parent=self)
|
|
869
968
|
|
|
870
969
|
reparent_ips.alters_data = True
|
|
871
970
|
|
|
@@ -1018,9 +1117,9 @@ class Prefix(PrimaryModel):
|
|
|
1018
1117
|
# IPv6, pool, or IPv4 /31-32 sets are fully usable
|
|
1019
1118
|
if any(
|
|
1020
1119
|
[
|
|
1021
|
-
self.ip_version ==
|
|
1120
|
+
self.ip_version == choices.IPAddressVersionChoices.VERSION_6,
|
|
1022
1121
|
self.type == choices.PrefixTypeChoices.TYPE_POOL,
|
|
1023
|
-
self.ip_version ==
|
|
1122
|
+
self.ip_version == choices.IPAddressVersionChoices.VERSION_4 and self.prefix_length >= 31,
|
|
1024
1123
|
]
|
|
1025
1124
|
):
|
|
1026
1125
|
return available_ips
|
|
@@ -1097,10 +1196,9 @@ class Prefix(PrimaryModel):
|
|
|
1097
1196
|
def get_utilization(self):
|
|
1098
1197
|
"""Return the utilization of this prefix as a UtilizationData object.
|
|
1099
1198
|
|
|
1100
|
-
For
|
|
1199
|
+
For CONTAINER and NETWORK prefixes, all child prefixes are considered fully utilized.
|
|
1101
1200
|
|
|
1102
|
-
For prefixes
|
|
1103
|
-
only IP addresses that are not contained within pools are added to the utilization.
|
|
1201
|
+
For NETWORK and POOL prefixes, individual IP addresses not already covered by a child prefix are also counted.
|
|
1104
1202
|
|
|
1105
1203
|
It is recommended that when using this method you add the following prefetch to the queryset when dealing with
|
|
1106
1204
|
multiple prefixes to ensure good performance:
|
|
@@ -1120,9 +1218,7 @@ class Prefix(PrimaryModel):
|
|
|
1120
1218
|
child_ips = netaddr.IPSet()
|
|
1121
1219
|
child_prefixes = netaddr.IPSet()
|
|
1122
1220
|
|
|
1123
|
-
#
|
|
1124
|
-
# and the addresses will instead be parented to the containing TYPE_NETWORK prefix. It should be possible to
|
|
1125
|
-
# change this when that is the case, see #3873 for historical context.
|
|
1221
|
+
# NETWORK and POOL prefixes, but not CONTAINER prefixes, count contained IPAddresses towards utilization.
|
|
1126
1222
|
if self.type != choices.PrefixTypeChoices.TYPE_CONTAINER:
|
|
1127
1223
|
pool_ips = IPAddress.objects.filter(
|
|
1128
1224
|
parent__namespace_id=self.namespace_id,
|
|
@@ -1132,6 +1228,7 @@ class Prefix(PrimaryModel):
|
|
|
1132
1228
|
).values_list("host", flat=True)
|
|
1133
1229
|
child_ips = netaddr.IPSet(pool_ips)
|
|
1134
1230
|
|
|
1231
|
+
# CONTAINER and NETWORK prefixes, but not POOL prefixes, count contained Prefixes towards utilization.
|
|
1135
1232
|
if self.type != choices.PrefixTypeChoices.TYPE_POOL:
|
|
1136
1233
|
# Using self.children.all over self.children.iterator (with chunk_size given or not) consistently shaves
|
|
1137
1234
|
# off around 200 extra SQL queries and shows better performance.
|
|
@@ -1141,13 +1238,13 @@ class Prefix(PrimaryModel):
|
|
|
1141
1238
|
|
|
1142
1239
|
numerator_set = child_ips | child_prefixes
|
|
1143
1240
|
|
|
1144
|
-
# Exclude network and broadcast
|
|
1241
|
+
# Exclude network and broadcast IPs from the denominator unless they're assigned to an IPAddress or child pool.
|
|
1145
1242
|
# Only applies to IPv4 network prefixes with a prefix length of /30 or shorter
|
|
1146
1243
|
if all(
|
|
1147
1244
|
[
|
|
1148
1245
|
denominator > 2,
|
|
1149
1246
|
self.type == choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
1150
|
-
self.ip_version ==
|
|
1247
|
+
self.ip_version == choices.IPAddressVersionChoices.VERSION_4,
|
|
1151
1248
|
]
|
|
1152
1249
|
):
|
|
1153
1250
|
if not any([self.network in numerator_set, self.broadcast in numerator_set]):
|
|
@@ -1266,12 +1363,26 @@ class IPAddress(PrimaryModel):
|
|
|
1266
1363
|
def __init__(self, *args, address=None, namespace=None, **kwargs):
|
|
1267
1364
|
super().__init__(*args, **kwargs)
|
|
1268
1365
|
|
|
1366
|
+
self._parent = None
|
|
1367
|
+
self._host = None
|
|
1368
|
+
|
|
1269
1369
|
if namespace is not None and not self.present_in_database:
|
|
1270
1370
|
self._provided_namespace = namespace
|
|
1271
1371
|
|
|
1272
1372
|
if address is not None and not self.present_in_database:
|
|
1273
1373
|
self._deconstruct_address(address)
|
|
1274
1374
|
|
|
1375
|
+
@classmethod
|
|
1376
|
+
def from_db(cls, db, field_names, values):
|
|
1377
|
+
instance = super().from_db(db, field_names, values)
|
|
1378
|
+
# These cached values are used to detect changes during save
|
|
1379
|
+
for field_name, value in zip(field_names, values):
|
|
1380
|
+
if field_name == "host":
|
|
1381
|
+
instance._host = value
|
|
1382
|
+
elif field_name == "parent":
|
|
1383
|
+
instance._parent = value
|
|
1384
|
+
return instance
|
|
1385
|
+
|
|
1275
1386
|
def __str__(self):
|
|
1276
1387
|
return str(self.address)
|
|
1277
1388
|
|
|
@@ -1297,33 +1408,36 @@ class IPAddress(PrimaryModel):
|
|
|
1297
1408
|
if self.host in empty_values or self.mask_length in empty_values:
|
|
1298
1409
|
return None
|
|
1299
1410
|
try:
|
|
1300
|
-
closest_parent = (
|
|
1301
|
-
|
|
1302
|
-
# 3.0 TODO: disallow IPAddress from parenting to a TYPE_POOL prefix, instead pick closest TYPE_NETWORK
|
|
1303
|
-
# .exclude(type=choices.PrefixTypeChoices.TYPE_POOL)
|
|
1304
|
-
.get_closest_parent(self.host, include_self=True)
|
|
1411
|
+
closest_parent = Prefix.objects.filter(namespace=self._namespace).get_closest_parent(
|
|
1412
|
+
self.host, include_self=True
|
|
1305
1413
|
)
|
|
1306
1414
|
return closest_parent
|
|
1307
1415
|
except Prefix.DoesNotExist as e:
|
|
1308
|
-
raise ValidationError(
|
|
1416
|
+
raise ValidationError(
|
|
1417
|
+
{"namespace": f"No suitable parent Prefix for {self.host} exists in Namespace {self._namespace}"}
|
|
1418
|
+
) from e
|
|
1309
1419
|
|
|
1310
1420
|
def clean(self):
|
|
1311
|
-
self.
|
|
1421
|
+
self._deconstruct_address(self.address)
|
|
1312
1422
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
ip_address = IPAddress.objects.get(id=self.id)
|
|
1316
|
-
if ip_address.host != self.host:
|
|
1317
|
-
raise ValidationError({"address": "Host address cannot be changed once created"})
|
|
1423
|
+
if self.present_in_database and self._host != self.host:
|
|
1424
|
+
raise ValidationError({"__all__": "Host address cannot be changed once created"})
|
|
1318
1425
|
|
|
1319
1426
|
# Validate IP status selection
|
|
1320
|
-
if
|
|
1427
|
+
if (
|
|
1428
|
+
self.type == choices.IPAddressTypeChoices.TYPE_SLAAC
|
|
1429
|
+
and self.ip_version != choices.IPAddressVersionChoices.VERSION_6
|
|
1430
|
+
):
|
|
1321
1431
|
raise ValidationError({"type": "Only IPv6 addresses can be assigned SLAAC type"})
|
|
1322
1432
|
|
|
1323
1433
|
closest_parent = self._get_closest_parent()
|
|
1324
|
-
# Validate `parent` can be used as the parent for this ipaddress
|
|
1325
1434
|
if closest_parent is not None:
|
|
1326
|
-
|
|
1435
|
+
# If `parent` was explicitly set or changed, validate it and reject if invalid.
|
|
1436
|
+
if (
|
|
1437
|
+
self.parent is not None
|
|
1438
|
+
and (self.parent != self._parent or not self.present_in_database)
|
|
1439
|
+
and self.parent != closest_parent
|
|
1440
|
+
):
|
|
1327
1441
|
raise ValidationError(
|
|
1328
1442
|
{
|
|
1329
1443
|
"parent": (
|
|
@@ -1332,14 +1446,10 @@ class IPAddress(PrimaryModel):
|
|
|
1332
1446
|
)
|
|
1333
1447
|
}
|
|
1334
1448
|
)
|
|
1449
|
+
# Otherwise, it was *implicitly* changed (e.g. by changing `namespace`), so just update it as appropriate.
|
|
1335
1450
|
self.parent = closest_parent
|
|
1336
1451
|
self._namespace = None
|
|
1337
1452
|
|
|
1338
|
-
# 3.0 TODO: uncomment the below to enforce this constraint
|
|
1339
|
-
# if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
|
|
1340
|
-
# err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
|
|
1341
|
-
# raise ValidationError({"address": err_msg})
|
|
1342
|
-
|
|
1343
1453
|
# Force dns_name to lowercase
|
|
1344
1454
|
if not self.dns_name.islower:
|
|
1345
1455
|
self.dns_name = self.dns_name.lower()
|
|
@@ -1353,6 +1463,9 @@ class IPAddress(PrimaryModel):
|
|
|
1353
1463
|
|
|
1354
1464
|
super().save(*args, **kwargs)
|
|
1355
1465
|
|
|
1466
|
+
self._parent = self.parent
|
|
1467
|
+
self._host = self.host
|
|
1468
|
+
|
|
1356
1469
|
@property
|
|
1357
1470
|
def address(self):
|
|
1358
1471
|
if self.host is not None and self.mask_length is not None:
|
|
@@ -1371,6 +1484,8 @@ class IPAddress(PrimaryModel):
|
|
|
1371
1484
|
Args:
|
|
1372
1485
|
ascending (bool): If set, reverses the return order.
|
|
1373
1486
|
"""
|
|
1487
|
+
if self.parent is None: # invalid, but possible currently
|
|
1488
|
+
return Prefix.objects.none()
|
|
1374
1489
|
return self.parent.ancestors(include_self=True, ascending=ascending)
|
|
1375
1490
|
|
|
1376
1491
|
@cached_property
|
|
@@ -1674,6 +1789,14 @@ class VLAN(PrimaryModel):
|
|
|
1674
1789
|
# Return all VM interfaces assigned to this VLAN
|
|
1675
1790
|
return VMInterface.objects.filter(Q(untagged_vlan_id=self.pk) | Q(tagged_vlans=self.pk)).distinct()
|
|
1676
1791
|
|
|
1792
|
+
@property
|
|
1793
|
+
def interfaces(self):
|
|
1794
|
+
return self.get_interfaces()
|
|
1795
|
+
|
|
1796
|
+
@property
|
|
1797
|
+
def vminterfaces(self):
|
|
1798
|
+
return self.get_vminterfaces()
|
|
1799
|
+
|
|
1677
1800
|
def clean(self):
|
|
1678
1801
|
super().clean()
|
|
1679
1802
|
|