nautobot 2.4.1__py3-none-any.whl → 2.4.3__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/templates/circuits/inc/circuit_termination.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +135 -0
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
- nautobot/circuits/tests/integration/test_relationships.py +1 -1
- nautobot/circuits/views.py +4 -1
- nautobot/cloud/api/views.py +3 -3
- nautobot/core/apps/__init__.py +0 -5
- nautobot/core/constants.py +0 -1
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +2 -1
- nautobot/core/forms/widgets.py +8 -0
- nautobot/core/management/commands/generate_performance_test_endpoints.py +268 -0
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_create.html +5 -0
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/widgets/clearable_file.html +5 -0
- nautobot/core/templatetags/helpers.py +3 -3
- nautobot/core/testing/integration.py +469 -12
- nautobot/core/tests/test_commands.py +31 -0
- nautobot/core/tests/test_jobs.py +34 -2
- nautobot/core/tests/test_utils.py +17 -2
- nautobot/core/utils/git.py +7 -2
- nautobot/core/utils/lookup.py +12 -1
- nautobot/core/views/generic.py +10 -2
- nautobot/core/views/mixins.py +22 -7
- nautobot/core/views/utils.py +2 -2
- nautobot/dcim/api/views.py +11 -10
- nautobot/dcim/forms.py +15 -6
- nautobot/dcim/models/devices.py +1 -2
- nautobot/dcim/tables/devices.py +2 -1
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_trace.html +4 -4
- nautobot/dcim/templates/dcim/consoleport.html +14 -4
- nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
- nautobot/dcim/templates/dcim/device/base.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/device_component.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/frontport.html +7 -2
- nautobot/dcim/templates/dcim/interface.html +9 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/locationtype.html +1 -1
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -1
- nautobot/dcim/templates/dcim/platform.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +9 -4
- nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +14 -4
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rackgroup.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation.html +2 -2
- nautobot/dcim/templates/dcim/rearport.html +7 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
- nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
- nautobot/dcim/tests/test_models.py +1 -1
- nautobot/dcim/tests/test_views.py +9 -1
- nautobot/dcim/views.py +12 -15
- nautobot/extras/api/serializers.py +33 -0
- nautobot/extras/api/views.py +13 -5
- nautobot/extras/constants.py +1 -0
- nautobot/extras/datasources/git.py +125 -0
- nautobot/extras/forms/forms.py +4 -0
- nautobot/extras/jobs.py +8 -1
- nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
- nautobot/extras/models/customfields.py +29 -12
- nautobot/extras/models/datasources.py +85 -0
- nautobot/extras/models/models.py +15 -0
- nautobot/extras/models/relationships.py +17 -5
- nautobot/extras/signals.py +15 -1
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/customlink.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/exporttemplate.html +1 -1
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/graphqlquery.html +1 -1
- nautobot/extras/templates/extras/job.html +1 -0
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
- nautobot/extras/templates/extras/jobhook.html +1 -1
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/plugin_detail.html +1 -1
- nautobot/extras/templates/extras/relationship.html +1 -63
- nautobot/extras/templates/extras/role_retrieve.html +1 -1
- nautobot/extras/templates/extras/scheduledjob.html +1 -1
- nautobot/extras/templates/extras/secret.html +1 -1
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/webhook.html +1 -1
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
- nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
- nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
- nautobot/extras/tests/git_helper.py +9 -1
- nautobot/extras/tests/integration/__init__.py +29 -16
- nautobot/extras/tests/test_api.py +6 -0
- nautobot/extras/tests/test_customfields.py +49 -51
- nautobot/extras/tests/test_datasources.py +27 -0
- nautobot/extras/tests/test_dynamicgroups.py +14 -0
- nautobot/extras/tests/test_models.py +283 -0
- nautobot/extras/tests/test_utils.py +22 -1
- nautobot/extras/tests/test_views.py +197 -9
- nautobot/extras/utils.py +47 -8
- nautobot/extras/views.py +84 -26
- nautobot/ipam/api/views.py +3 -3
- nautobot/ipam/forms.py +2 -6
- nautobot/ipam/models.py +8 -2
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/templates/ipam/ipaddress.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/rir.html +1 -1
- nautobot/ipam/templates/ipam/routetarget.html +1 -1
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +1 -1
- nautobot/ipam/templates/ipam/vrf.html +1 -1
- nautobot/ipam/tests/test_models.py +24 -0
- nautobot/ipam/tests/test_utils.py +41 -2
- nautobot/ipam/utils/__init__.py +18 -11
- nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
- nautobot/project-static/docs/404.html +87 -12
- nautobot/project-static/docs/apps/index.html +88 -13
- nautobot/project-static/docs/apps/nautobot-apps.html +88 -13
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js → bundle.60a45f97.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/{bundle.88dd0f4e.min.js.map → bundle.60a45f97.min.js.map} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
- nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +87 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +177 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +114 -17
- nautobot/project-static/docs/development/apps/api/configuration-view.html +87 -12
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/global-search.html +87 -12
- nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -21
- nautobot/project-static/docs/development/apps/api/models/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +89 -14
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +87 -12
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +87 -12
- nautobot/project-static/docs/development/apps/api/prometheus.html +87 -12
- nautobot/project-static/docs/development/apps/api/setup.html +88 -13
- nautobot/project-static/docs/development/apps/api/testing.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +87 -12
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/base-template.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/index.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/notes.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +87 -12
- nautobot/project-static/docs/development/apps/api/views/urls.html +87 -12
- nautobot/project-static/docs/development/apps/index.html +87 -12
- nautobot/project-static/docs/development/apps/migration/code-updates.html +93 -17
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +89 -14
- nautobot/project-static/docs/development/apps/migration/from-v1.html +90 -15
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +87 -12
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +87 -12
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +88 -13
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +87 -12
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +87 -12
- nautobot/project-static/docs/development/core/application-registry.html +87 -12
- nautobot/project-static/docs/development/core/best-practices.html +88 -13
- nautobot/project-static/docs/development/core/bootstrap-ui.html +88 -13
- nautobot/project-static/docs/development/core/caching.html +87 -12
- nautobot/project-static/docs/development/core/controllers.html +87 -12
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +94 -19
- nautobot/project-static/docs/development/core/generic-views.html +87 -12
- nautobot/project-static/docs/development/core/getting-started.html +89 -14
- nautobot/project-static/docs/development/core/homepage.html +87 -12
- nautobot/project-static/docs/development/core/index.html +88 -13
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +90 -15
- nautobot/project-static/docs/development/core/model-checklist.html +88 -13
- nautobot/project-static/docs/development/core/model-features.html +87 -12
- nautobot/project-static/docs/development/core/natural-keys.html +87 -12
- nautobot/project-static/docs/development/core/navigation-menu.html +88 -13
- nautobot/project-static/docs/development/core/release-checklist.html +88 -13
- nautobot/project-static/docs/development/core/role-internals.html +87 -12
- nautobot/project-static/docs/development/core/settings.html +88 -13
- nautobot/project-static/docs/development/core/style-guide.html +91 -16
- nautobot/project-static/docs/development/core/templates.html +88 -13
- nautobot/project-static/docs/development/core/testing.html +87 -12
- nautobot/project-static/docs/development/core/ui-component-framework.html +87 -12
- nautobot/project-static/docs/development/core/user-preferences.html +87 -12
- nautobot/project-static/docs/development/index.html +87 -12
- nautobot/project-static/docs/development/jobs/index.html +95 -13
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +90 -14
- nautobot/project-static/docs/index.html +90 -14
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +89 -14
- nautobot/project-static/docs/overview/design_philosophy.html +87 -12
- nautobot/project-static/docs/release-notes/index.html +87 -12
- nautobot/project-static/docs/release-notes/version-1.0.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.1.html +89 -14
- nautobot/project-static/docs/release-notes/version-1.2.html +90 -15
- nautobot/project-static/docs/release-notes/version-1.3.html +88 -13
- nautobot/project-static/docs/release-notes/version-1.4.html +104 -29
- nautobot/project-static/docs/release-notes/version-1.5.html +95 -20
- nautobot/project-static/docs/release-notes/version-1.6.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.0.html +97 -22
- nautobot/project-static/docs/release-notes/version-2.1.html +94 -19
- nautobot/project-static/docs/release-notes/version-2.2.html +88 -13
- nautobot/project-static/docs/release-notes/version-2.3.html +91 -16
- nautobot/project-static/docs/release-notes/version-2.4.html +465 -12
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +296 -288
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +91 -16
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +88 -13
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +90 -15
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +95 -20
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +90 -15
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +88 -13
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +91 -16
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +87 -12
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +102 -27
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +89 -14
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +88 -13
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +93 -18
- nautobot/project-static/docs/user-guide/administration/installation/services.html +88 -13
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +87 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +87 -12
- nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9844 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +87 -12
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +88 -13
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +87 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +98 -20
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +87 -12
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +99 -24
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +90 -15
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +188 -30
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +87 -12
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +87 -12
- nautobot/project-static/docs/user-guide/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +88 -13
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +407 -14
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +90 -15
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +89 -14
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +93 -18
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +87 -12
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +87 -12
- nautobot/project-static/js/dropdown.js +28 -0
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/templates/tenancy/tenant.html +1 -2
- nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
- nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
- nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
- nautobot/tenancy/tests/test_views.py +5 -1
- nautobot/tenancy/urls.py +7 -79
- nautobot/tenancy/views.py +51 -80
- nautobot/virtualization/templates/virtualization/cluster.html +1 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
- nautobot/wireless/api/serializers.py +6 -1
- nautobot/wireless/api/views.py +3 -3
- nautobot/wireless/tests/test_api.py +5 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/METADATA +12 -12
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/RECORD +459 -443
- nautobot/dcim/tests/integration/test_device_bulk_delete.py +0 -189
- nautobot/dcim/tests/integration/test_device_bulk_edit.py +0 -181
- /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/WHEEL +0 -0
- {nautobot-2.4.1.dist-info → nautobot-2.4.3.dist-info}/entry_points.txt +0 -0
nautobot/extras/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ import hmac
|
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
|
+
from typing import Optional
|
|
9
10
|
|
|
10
11
|
from django.apps import apps
|
|
11
12
|
from django.conf import settings
|
|
@@ -13,7 +14,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
13
14
|
from django.core.cache import cache
|
|
14
15
|
from django.core.validators import ValidationError
|
|
15
16
|
from django.db import transaction
|
|
16
|
-
from django.db.models import Q
|
|
17
|
+
from django.db.models import Model, Q
|
|
17
18
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
18
19
|
from django.utils.deconstruct import deconstructible
|
|
19
20
|
import kubernetes.client
|
|
@@ -21,9 +22,12 @@ import redis.exceptions
|
|
|
21
22
|
|
|
22
23
|
from nautobot.core.choices import ColorChoices
|
|
23
24
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
25
|
+
from nautobot.core.exceptions import FilterSetFieldNotFound
|
|
24
26
|
from nautobot.core.models.managers import TagsManager
|
|
25
27
|
from nautobot.core.models.utils import find_models_with_matching_fields
|
|
26
28
|
from nautobot.core.utils.data import is_uuid
|
|
29
|
+
from nautobot.core.utils.lookup import get_filterset_for_model, get_model_for_view_name
|
|
30
|
+
from nautobot.core.utils.requests import is_single_choice_field
|
|
27
31
|
from nautobot.extras.choices import DynamicGroupTypeChoices, JobQueueTypeChoices, ObjectChangeActionChoices
|
|
28
32
|
from nautobot.extras.constants import (
|
|
29
33
|
CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL,
|
|
@@ -36,16 +40,24 @@ from nautobot.extras.registry import registry
|
|
|
36
40
|
logger = logging.getLogger(__name__)
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def get_base_template(base_template, model):
|
|
43
|
+
def get_base_template(base_template: Optional[str], model: type[Model]) -> str:
|
|
40
44
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
Attempt to locate the correct base template for an object detail view and related views, if one was not specified.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
base_template (str, optional): If not None, this explicitly specified template will be preferred.
|
|
49
|
+
model (Model): The model to identify a base template for, if base_template is None.
|
|
50
|
+
|
|
51
|
+
Returns the specified `base_template`, if not `None`.
|
|
52
|
+
Otherwise, if `"<app>/<model_name>.html"` exists (legacy ObjectView pattern), returns that string.
|
|
53
|
+
Otherwise, if `"<app>/<model_name>_retrieve.html"` exists (as used in `NautobotUIViewSet`), returns that string.
|
|
54
|
+
If all else fails, returns `"generic/object_retrieve.html"`.
|
|
55
|
+
|
|
56
|
+
Note: before Nautobot 2.4.2, this API would default to "base.html" rather than "generic/object_retrieve.html".
|
|
57
|
+
This behavior was changed to the current behavior to address issue #6550 and similar incorrect behavior.
|
|
45
58
|
"""
|
|
46
59
|
if base_template is None:
|
|
47
60
|
base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
|
|
48
|
-
# 2.0 TODO(Hanlin): This can be removed once an object view has been established for every model.
|
|
49
61
|
try:
|
|
50
62
|
get_template(base_template)
|
|
51
63
|
except TemplateDoesNotExist:
|
|
@@ -53,7 +65,7 @@ def get_base_template(base_template, model):
|
|
|
53
65
|
try:
|
|
54
66
|
get_template(base_template)
|
|
55
67
|
except TemplateDoesNotExist:
|
|
56
|
-
base_template = "
|
|
68
|
+
base_template = "generic/object_retrieve.html"
|
|
57
69
|
return base_template
|
|
58
70
|
|
|
59
71
|
|
|
@@ -871,3 +883,30 @@ def bulk_delete_with_bulk_change_logging(qs, batch_size=1000):
|
|
|
871
883
|
finally:
|
|
872
884
|
change_context.defer_object_changes = False
|
|
873
885
|
change_context.reset_deferred_object_changes()
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def fixup_filterset_query_params(param_dict, view_name, non_filter_params):
|
|
889
|
+
"""
|
|
890
|
+
Called before saving query filter parameters to a SavedView's config. This function will format
|
|
891
|
+
single value query parameters to be saved as a single values instead of lists of singles values.
|
|
892
|
+
|
|
893
|
+
Args:
|
|
894
|
+
param_dict (dict): key-value pairs of query parameters.
|
|
895
|
+
view_name (str): The name of the view that the saved view is associated with. "dcim:location_list" for example.
|
|
896
|
+
non_filter_params (list): List of non-query parameters that should not be formatted.
|
|
897
|
+
"""
|
|
898
|
+
model = get_model_for_view_name(view_name)
|
|
899
|
+
try:
|
|
900
|
+
filterset_class = get_filterset_for_model(model)
|
|
901
|
+
except TypeError:
|
|
902
|
+
return param_dict
|
|
903
|
+
|
|
904
|
+
filterset = filterset_class()
|
|
905
|
+
|
|
906
|
+
for filter_field, value in param_dict.items():
|
|
907
|
+
try:
|
|
908
|
+
if filter_field not in non_filter_params and is_single_choice_field(filterset, filter_field):
|
|
909
|
+
param_dict[filter_field] = value[0]
|
|
910
|
+
except FilterSetFieldNotFound:
|
|
911
|
+
pass
|
|
912
|
+
return param_dict
|
nautobot/extras/views.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from typing import Optional
|
|
2
3
|
from urllib.parse import parse_qs
|
|
3
4
|
|
|
4
5
|
from django.contrib import messages
|
|
@@ -25,21 +26,24 @@ from rest_framework.permissions import IsAuthenticated
|
|
|
25
26
|
|
|
26
27
|
from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
|
|
27
28
|
from nautobot.core.events import publish_event
|
|
29
|
+
from nautobot.core.exceptions import FilterSetFieldNotFound
|
|
28
30
|
from nautobot.core.forms import restrict_form_fields
|
|
29
31
|
from nautobot.core.models.querysets import count_related
|
|
30
32
|
from nautobot.core.models.utils import pretty_print_query, serialize_object_v2
|
|
31
33
|
from nautobot.core.tables import ButtonsColumn
|
|
32
34
|
from nautobot.core.ui import object_detail
|
|
33
35
|
from nautobot.core.ui.choices import SectionChoices
|
|
36
|
+
from nautobot.core.ui.object_detail import ObjectDetailContent, ObjectFieldsPanel
|
|
34
37
|
from nautobot.core.utils.config import get_settings_or_config
|
|
35
38
|
from nautobot.core.utils.lookup import (
|
|
36
39
|
get_filterset_for_model,
|
|
40
|
+
get_model_for_view_name,
|
|
37
41
|
get_route_for_model,
|
|
38
42
|
get_table_class_string_from_view_name,
|
|
39
43
|
get_table_for_model,
|
|
40
44
|
)
|
|
41
45
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
42
|
-
from nautobot.core.utils.requests import normalize_querydict
|
|
46
|
+
from nautobot.core.utils.requests import is_single_choice_field, normalize_querydict
|
|
43
47
|
from nautobot.core.views import generic, viewsets
|
|
44
48
|
from nautobot.core.views.mixins import (
|
|
45
49
|
GetReturnURLMixin,
|
|
@@ -67,7 +71,7 @@ from nautobot.dcim.tables import (
|
|
|
67
71
|
VirtualDeviceContextTable,
|
|
68
72
|
)
|
|
69
73
|
from nautobot.extras.context_managers import deferred_change_logging_for_bulk_operation
|
|
70
|
-
from nautobot.extras.utils import get_base_template, get_job_queue, get_worker_count
|
|
74
|
+
from nautobot.extras.utils import fixup_filterset_query_params, get_base_template, get_job_queue, get_worker_count
|
|
71
75
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
72
76
|
from nautobot.ipam.tables import IPAddressTable, PrefixTable, VLANTable
|
|
73
77
|
from nautobot.virtualization.models import VirtualMachine, VMInterface
|
|
@@ -718,8 +722,13 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
718
722
|
|
|
719
723
|
if table_class is not None:
|
|
720
724
|
# Members table (for display on Members nav tab)
|
|
725
|
+
if hasattr(members, "without_tree_fields"):
|
|
726
|
+
members = members.without_tree_fields()
|
|
721
727
|
members_table = table_class(
|
|
722
|
-
members.restrict(request.user, "view"),
|
|
728
|
+
members.restrict(request.user, "view"),
|
|
729
|
+
orderable=False,
|
|
730
|
+
exclude=["dynamic_group_count"],
|
|
731
|
+
hide_hierarchy_ui=True,
|
|
723
732
|
)
|
|
724
733
|
paginate = {
|
|
725
734
|
"paginator_class": EnhancedPaginator,
|
|
@@ -900,10 +909,13 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
900
909
|
class ObjectDynamicGroupsView(generic.GenericView):
|
|
901
910
|
"""
|
|
902
911
|
Present a list of dynamic groups associated to a particular object.
|
|
903
|
-
|
|
912
|
+
|
|
913
|
+
base_template: Specify to explicitly identify the base object detail template to render.
|
|
914
|
+
If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
|
|
915
|
+
will be used, as per `get_base_template()`.
|
|
904
916
|
"""
|
|
905
917
|
|
|
906
|
-
base_template = None
|
|
918
|
+
base_template: Optional[str] = None
|
|
907
919
|
|
|
908
920
|
def get(self, request, model, **kwargs):
|
|
909
921
|
# Handle QuerySet restriction of parent object if needed
|
|
@@ -926,7 +938,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
926
938
|
}
|
|
927
939
|
RequestConfig(request, paginate).configure(dynamicgroups_table)
|
|
928
940
|
|
|
929
|
-
|
|
941
|
+
base_template = get_base_template(self.base_template, model)
|
|
930
942
|
|
|
931
943
|
return render(
|
|
932
944
|
request,
|
|
@@ -936,7 +948,7 @@ class ObjectDynamicGroupsView(generic.GenericView):
|
|
|
936
948
|
"verbose_name": obj._meta.verbose_name,
|
|
937
949
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
938
950
|
"table": dynamicgroups_table,
|
|
939
|
-
"base_template":
|
|
951
|
+
"base_template": base_template,
|
|
940
952
|
"active_tab": "dynamic-groups",
|
|
941
953
|
},
|
|
942
954
|
)
|
|
@@ -1260,9 +1272,10 @@ class JobListView(generic.ObjectListView):
|
|
|
1260
1272
|
def alter_queryset(self, request):
|
|
1261
1273
|
queryset = super().alter_queryset(request)
|
|
1262
1274
|
# Default to hiding "hidden" and non-installed jobs
|
|
1263
|
-
|
|
1275
|
+
filter_params = self.get_filter_params(request)
|
|
1276
|
+
if "hidden" not in filter_params:
|
|
1264
1277
|
queryset = queryset.filter(hidden=False)
|
|
1265
|
-
if "installed" not in
|
|
1278
|
+
if "installed" not in filter_params:
|
|
1266
1279
|
queryset = queryset.filter(installed=True)
|
|
1267
1280
|
return queryset
|
|
1268
1281
|
|
|
@@ -1801,15 +1814,23 @@ class SavedViewUIViewSet(
|
|
|
1801
1814
|
if sort_order:
|
|
1802
1815
|
sv.config["sort_order"] = sort_order
|
|
1803
1816
|
|
|
1817
|
+
model = get_model_for_view_name(sv.view)
|
|
1818
|
+
filterset_class = get_filterset_for_model(model)
|
|
1819
|
+
filterset = filterset_class()
|
|
1804
1820
|
filter_params = {}
|
|
1805
1821
|
for key in request.GET:
|
|
1806
1822
|
if key in self.non_filter_params:
|
|
1807
1823
|
continue
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1824
|
+
try:
|
|
1825
|
+
if is_single_choice_field(filterset, key):
|
|
1826
|
+
filter_params[key] = request.GET.getlist(key)[0]
|
|
1827
|
+
except FilterSetFieldNotFound:
|
|
1828
|
+
continue
|
|
1829
|
+
try:
|
|
1830
|
+
if not is_single_choice_field(filterset, key):
|
|
1831
|
+
filter_params[key] = request.GET.getlist(key)
|
|
1832
|
+
except FilterSetFieldNotFound:
|
|
1833
|
+
continue
|
|
1813
1834
|
|
|
1814
1835
|
if filter_params:
|
|
1815
1836
|
sv.config["filter_params"] = filter_params
|
|
@@ -1834,14 +1855,14 @@ class SavedViewUIViewSet(
|
|
|
1834
1855
|
and the name of the new SavedView from request.POST to create a new SavedView.
|
|
1835
1856
|
"""
|
|
1836
1857
|
name = request.POST.get("name")
|
|
1858
|
+
view_name = request.POST.get("view")
|
|
1837
1859
|
is_shared = request.POST.get("is_shared", False)
|
|
1838
1860
|
if is_shared:
|
|
1839
1861
|
is_shared = True
|
|
1840
1862
|
params = request.POST.get("params", "")
|
|
1863
|
+
param_dict = fixup_filterset_query_params(parse_qs(params), view_name, self.non_filter_params)
|
|
1841
1864
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "q", "per_page"]
|
|
1865
|
+
single_value_params = ["saved_view", "table_changes_pending", "all_filters_removed", "per_page"]
|
|
1845
1866
|
for key in param_dict.keys():
|
|
1846
1867
|
if key in single_value_params:
|
|
1847
1868
|
param_dict[key] = param_dict[key][0]
|
|
@@ -1850,7 +1871,6 @@ class SavedViewUIViewSet(
|
|
|
1850
1871
|
derived_instance = None
|
|
1851
1872
|
if derived_view_pk:
|
|
1852
1873
|
derived_instance = self.get_queryset().get(pk=derived_view_pk)
|
|
1853
|
-
view_name = request.POST.get("view")
|
|
1854
1874
|
try:
|
|
1855
1875
|
reverse(view_name)
|
|
1856
1876
|
except NoReverseMatch:
|
|
@@ -2185,10 +2205,13 @@ class ObjectChangeView(generic.ObjectView):
|
|
|
2185
2205
|
class ObjectChangeLogView(generic.GenericView):
|
|
2186
2206
|
"""
|
|
2187
2207
|
Present a history of changes made to a particular object.
|
|
2188
|
-
|
|
2208
|
+
|
|
2209
|
+
base_template: Specify to explicitly identify the base object detail template to render.
|
|
2210
|
+
If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
|
|
2211
|
+
will be used, as per `get_base_template()`.
|
|
2189
2212
|
"""
|
|
2190
2213
|
|
|
2191
|
-
base_template = None
|
|
2214
|
+
base_template: Optional[str] = None
|
|
2192
2215
|
|
|
2193
2216
|
def get(self, request, model, **kwargs):
|
|
2194
2217
|
# Handle QuerySet restriction of parent object if needed
|
|
@@ -2216,7 +2239,7 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
2216
2239
|
}
|
|
2217
2240
|
RequestConfig(request, paginate).configure(objectchanges_table)
|
|
2218
2241
|
|
|
2219
|
-
|
|
2242
|
+
base_template = get_base_template(self.base_template, model)
|
|
2220
2243
|
|
|
2221
2244
|
return render(
|
|
2222
2245
|
request,
|
|
@@ -2226,7 +2249,7 @@ class ObjectChangeLogView(generic.GenericView):
|
|
|
2226
2249
|
"verbose_name": obj._meta.verbose_name,
|
|
2227
2250
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
2228
2251
|
"table": objectchanges_table,
|
|
2229
|
-
"base_template":
|
|
2252
|
+
"base_template": base_template,
|
|
2230
2253
|
"active_tab": "changelog",
|
|
2231
2254
|
},
|
|
2232
2255
|
)
|
|
@@ -2319,10 +2342,13 @@ class NoteDeleteView(generic.ObjectDeleteView):
|
|
|
2319
2342
|
class ObjectNotesView(generic.GenericView):
|
|
2320
2343
|
"""
|
|
2321
2344
|
Present a list of notes associated to a particular object.
|
|
2322
|
-
|
|
2345
|
+
|
|
2346
|
+
base_template: Specify to explicitly identify the base object detail template to render.
|
|
2347
|
+
If not provided, "<app>/<model>.html", "<app>/<model>_retrieve.html", or "generic/object_retrieve.html"
|
|
2348
|
+
will be used, as per `get_base_template()`.
|
|
2323
2349
|
"""
|
|
2324
2350
|
|
|
2325
|
-
base_template = None
|
|
2351
|
+
base_template: Optional[str] = None
|
|
2326
2352
|
|
|
2327
2353
|
def get(self, request, model, **kwargs):
|
|
2328
2354
|
# Handle QuerySet restriction of parent object if needed
|
|
@@ -2346,7 +2372,7 @@ class ObjectNotesView(generic.GenericView):
|
|
|
2346
2372
|
}
|
|
2347
2373
|
RequestConfig(request, paginate).configure(notes_table)
|
|
2348
2374
|
|
|
2349
|
-
|
|
2375
|
+
base_template = get_base_template(self.base_template, model)
|
|
2350
2376
|
|
|
2351
2377
|
return render(
|
|
2352
2378
|
request,
|
|
@@ -2356,7 +2382,7 @@ class ObjectNotesView(generic.GenericView):
|
|
|
2356
2382
|
"verbose_name": obj._meta.verbose_name,
|
|
2357
2383
|
"verbose_name_plural": obj._meta.verbose_name_plural,
|
|
2358
2384
|
"table": notes_table,
|
|
2359
|
-
"base_template":
|
|
2385
|
+
"base_template": base_template,
|
|
2360
2386
|
"active_tab": "notes",
|
|
2361
2387
|
"form": notes_form,
|
|
2362
2388
|
},
|
|
@@ -2378,6 +2404,38 @@ class RelationshipListView(generic.ObjectListView):
|
|
|
2378
2404
|
|
|
2379
2405
|
class RelationshipView(generic.ObjectView):
|
|
2380
2406
|
queryset = Relationship.objects.all()
|
|
2407
|
+
object_detail_content = ObjectDetailContent(
|
|
2408
|
+
panels=(
|
|
2409
|
+
ObjectFieldsPanel(
|
|
2410
|
+
label="Relationship",
|
|
2411
|
+
section=SectionChoices.LEFT_HALF,
|
|
2412
|
+
weight=100,
|
|
2413
|
+
fields="__all__",
|
|
2414
|
+
exclude_fields=[
|
|
2415
|
+
"source_type",
|
|
2416
|
+
"source_label",
|
|
2417
|
+
"source_hidden",
|
|
2418
|
+
"source_filter",
|
|
2419
|
+
"destination_type",
|
|
2420
|
+
"destination_label",
|
|
2421
|
+
"destination_hidden",
|
|
2422
|
+
"destination_filter",
|
|
2423
|
+
],
|
|
2424
|
+
),
|
|
2425
|
+
ObjectFieldsPanel(
|
|
2426
|
+
label="Source Attributes",
|
|
2427
|
+
section=SectionChoices.RIGHT_HALF,
|
|
2428
|
+
weight=100,
|
|
2429
|
+
fields=["source_type", "source_label", "source_hidden", "source_filter"],
|
|
2430
|
+
),
|
|
2431
|
+
ObjectFieldsPanel(
|
|
2432
|
+
label="Destination Attributes",
|
|
2433
|
+
section=SectionChoices.RIGHT_HALF,
|
|
2434
|
+
weight=200,
|
|
2435
|
+
fields=["destination_type", "destination_label", "destination_hidden", "destination_filter"],
|
|
2436
|
+
),
|
|
2437
|
+
)
|
|
2438
|
+
)
|
|
2381
2439
|
|
|
2382
2440
|
|
|
2383
2441
|
class RelationshipEditView(generic.ObjectEditView):
|
nautobot/ipam/api/views.py
CHANGED
|
@@ -13,7 +13,7 @@ from nautobot.core.constants import MAX_PAGE_SIZE_DEFAULT, PAGINATE_COUNT_DEFAUL
|
|
|
13
13
|
from nautobot.core.models.querysets import count_related
|
|
14
14
|
from nautobot.core.utils.config import get_settings_or_config
|
|
15
15
|
from nautobot.dcim.models import Location
|
|
16
|
-
from nautobot.extras.api.views import NautobotModelViewSet
|
|
16
|
+
from nautobot.extras.api.views import ModelViewSet, NautobotModelViewSet
|
|
17
17
|
from nautobot.ipam import filters
|
|
18
18
|
from nautobot.ipam.api import serializers
|
|
19
19
|
from nautobot.ipam.models import (
|
|
@@ -323,7 +323,7 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
323
323
|
return Response(serializer.data)
|
|
324
324
|
|
|
325
325
|
|
|
326
|
-
class PrefixLocationAssignmentViewSet(
|
|
326
|
+
class PrefixLocationAssignmentViewSet(ModelViewSet):
|
|
327
327
|
queryset = PrefixLocationAssignment.objects.all()
|
|
328
328
|
serializer_class = serializers.PrefixLocationAssignmentSerializer
|
|
329
329
|
filterset_class = filters.PrefixLocationAssignmentFilterSet
|
|
@@ -581,7 +581,7 @@ class VLANViewSet(NautobotModelViewSet):
|
|
|
581
581
|
raise self.LocationIncompatibleLegacyBehavior from e
|
|
582
582
|
|
|
583
583
|
|
|
584
|
-
class VLANLocationAssignmentViewSet(
|
|
584
|
+
class VLANLocationAssignmentViewSet(ModelViewSet):
|
|
585
585
|
queryset = VLANLocationAssignment.objects.all()
|
|
586
586
|
serializer_class = serializers.VLANLocationAssignmentSerializer
|
|
587
587
|
filterset_class = filters.VLANLocationAssignmentFilterSet
|
nautobot/ipam/forms.py
CHANGED
|
@@ -674,13 +674,9 @@ class IPAddressFilterForm(NautobotFilterForm, TenancyFilterForm, StatusModelFilt
|
|
|
674
674
|
"has_nat_inside",
|
|
675
675
|
]
|
|
676
676
|
q = forms.CharField(required=False, label="Search")
|
|
677
|
-
parent =
|
|
677
|
+
parent = DynamicModelMultipleChoiceField(
|
|
678
|
+
queryset=Prefix.objects.all(),
|
|
678
679
|
required=False,
|
|
679
|
-
widget=forms.TextInput(
|
|
680
|
-
attrs={
|
|
681
|
-
"placeholder": "Prefix",
|
|
682
|
-
}
|
|
683
|
-
),
|
|
684
680
|
label="Parent Prefix",
|
|
685
681
|
)
|
|
686
682
|
ip_version = forms.ChoiceField(
|
nautobot/ipam/models.py
CHANGED
|
@@ -975,7 +975,10 @@ class Prefix(PrimaryModel):
|
|
|
975
975
|
while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
|
|
976
976
|
"""
|
|
977
977
|
return IPAddress.objects.filter(
|
|
978
|
-
parent__namespace=self.namespace,
|
|
978
|
+
parent__namespace=self.namespace,
|
|
979
|
+
ip_version=self.ip_version,
|
|
980
|
+
host__gte=self.network,
|
|
981
|
+
host__lte=self.broadcast,
|
|
979
982
|
)
|
|
980
983
|
|
|
981
984
|
def get_first_available_prefix(self):
|
|
@@ -1016,7 +1019,10 @@ class Prefix(PrimaryModel):
|
|
|
1016
1019
|
# change this when that is the case, see #3873 for historical context.
|
|
1017
1020
|
if self.type != choices.PrefixTypeChoices.TYPE_CONTAINER:
|
|
1018
1021
|
pool_ips = IPAddress.objects.filter(
|
|
1019
|
-
parent__namespace=self.namespace,
|
|
1022
|
+
parent__namespace=self.namespace,
|
|
1023
|
+
ip_version=self.ip_version,
|
|
1024
|
+
host__gte=self.network,
|
|
1025
|
+
host__lte=self.broadcast,
|
|
1020
1026
|
).values_list("host", flat=True)
|
|
1021
1027
|
child_ips = netaddr.IPSet(pool_ips)
|
|
1022
1028
|
|
nautobot/ipam/tables.py
CHANGED
|
@@ -79,7 +79,7 @@ IPADDRESS_LINK = """
|
|
|
79
79
|
{% elif perms.ipam.add_ipaddress %}
|
|
80
80
|
<a href="\
|
|
81
81
|
{% url 'ipam:ipaddress_add' %}\
|
|
82
|
-
?address={{ record.1 }}\
|
|
82
|
+
?address={{ record.1 }}&namespace={{ object.namespace.pk }}\
|
|
83
83
|
{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}\
|
|
84
84
|
{% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}\
|
|
85
85
|
" class="btn btn-xs btn-success">\
|
|
@@ -101,7 +101,7 @@ IPADDRESS_COPY_LINK = """
|
|
|
101
101
|
{% elif perms.ipam.add_ipaddress %}
|
|
102
102
|
<a href="\
|
|
103
103
|
{% url 'ipam:ipaddress_add' %}\
|
|
104
|
-
?address={{ record.1 }}\
|
|
104
|
+
?address={{ record.1 }}&namespace={{ object.namespace.pk }}\
|
|
105
105
|
{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}\
|
|
106
106
|
{% if object.tenant %}&tenant={{ object.tenant.pk }}{% endif %}\
|
|
107
107
|
" class="btn btn-xs btn-success">\
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{% extends 'generic/
|
|
1
|
+
{% extends 'generic/object_retrieve.html' %}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{% extends 'generic/
|
|
1
|
+
{% extends 'generic/object_retrieve.html' %}
|
|
@@ -827,6 +827,24 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
827
827
|
|
|
828
828
|
self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
|
|
829
829
|
|
|
830
|
+
def test_get_all_ips_issue_3319(self):
|
|
831
|
+
# https://github.com/nautobot/nautobot/issues/3319
|
|
832
|
+
# Confirm that IPv4 addresses aren't caught up in the IPv6 ::/96 subnet by accident, and vice versa.
|
|
833
|
+
prefix_v6 = Prefix.objects.create(
|
|
834
|
+
prefix="::/0", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
|
|
835
|
+
)
|
|
836
|
+
prefix_v4 = Prefix.objects.create(
|
|
837
|
+
prefix="0.0.0.0/0", type=PrefixTypeChoices.TYPE_CONTAINER, status=self.status, namespace=self.namespace
|
|
838
|
+
)
|
|
839
|
+
IPAddress.objects.create(address="::0102:0304/128", status=self.status, namespace=self.namespace)
|
|
840
|
+
IPAddress.objects.create(address="1.2.3.4/32", status=self.status, namespace=self.namespace)
|
|
841
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
842
|
+
prefix_v6.get_all_ips(), IPAddress.objects.filter(ip_version=6, parent__namespace=self.namespace)
|
|
843
|
+
)
|
|
844
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
845
|
+
prefix_v4.get_all_ips(), IPAddress.objects.filter(ip_version=4, parent__namespace=self.namespace)
|
|
846
|
+
)
|
|
847
|
+
|
|
830
848
|
def test_get_utilization(self):
|
|
831
849
|
# Container Prefix
|
|
832
850
|
prefix = Prefix.objects.create(
|
|
@@ -982,6 +1000,12 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
982
1000
|
Prefix.objects.create(prefix="ab80::/9", status=self.status, namespace=self.namespace)
|
|
983
1001
|
self.assertEqual(large_prefix_v6.get_utilization(), (2**120, 2**120))
|
|
984
1002
|
|
|
1003
|
+
# https://github.com/nautobot/nautobot/issues/3319
|
|
1004
|
+
v4_10dot_address_space_in_v6 = Prefix.objects.create(
|
|
1005
|
+
prefix="0a00::/8", type=PrefixTypeChoices.TYPE_NETWORK, status=self.status, namespace=self.namespace
|
|
1006
|
+
)
|
|
1007
|
+
self.assertSequenceEqual(v4_10dot_address_space_in_v6.get_utilization(), (0, 2**120))
|
|
1008
|
+
|
|
985
1009
|
#
|
|
986
1010
|
# Uniqueness enforcement tests
|
|
987
1011
|
#
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from django.test import TestCase
|
|
2
|
+
import netaddr
|
|
2
3
|
|
|
3
4
|
from nautobot.core.forms.utils import parse_numeric_range
|
|
4
5
|
from nautobot.extras.models import Status
|
|
5
|
-
from nautobot.ipam.models import VLAN, VLANGroup
|
|
6
|
-
from nautobot.ipam.utils import add_available_vlans
|
|
6
|
+
from nautobot.ipam.models import IPAddress, Namespace, Prefix, VLAN, VLANGroup
|
|
7
|
+
from nautobot.ipam.utils import add_available_ipaddresses, add_available_vlans
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class AddAvailableVlansTest(TestCase):
|
|
@@ -27,6 +28,44 @@ class AddAvailableVlansTest(TestCase):
|
|
|
27
28
|
)
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
class AddAvailableIPsTest(TestCase):
|
|
32
|
+
"""Tests for add_available_ipaddresses()."""
|
|
33
|
+
|
|
34
|
+
def test_add_available_ipaddresses_ipv4(self):
|
|
35
|
+
prefix = Prefix.objects.create(prefix="22.22.22.0/24", status=Status.objects.get_for_model(Prefix).first())
|
|
36
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
37
|
+
# .0 isn't available since this isn't a Pool prefix
|
|
38
|
+
available_1 = (9, "22.22.22.1/24")
|
|
39
|
+
ip_1 = IPAddress.objects.create(address="22.22.22.10/24", status=ip_status)
|
|
40
|
+
available_2 = (10, "22.22.22.11/24")
|
|
41
|
+
ip_2 = IPAddress.objects.create(address="22.22.22.21/24", status=ip_status)
|
|
42
|
+
available_3 = (233, "22.22.22.22/24")
|
|
43
|
+
# .255 isn't available since this isn't a Pool prefix
|
|
44
|
+
self.assertEqual(
|
|
45
|
+
add_available_ipaddresses(prefix=netaddr.IPNetwork(prefix.prefix), ipaddress_list=(ip_1, ip_2)),
|
|
46
|
+
[available_1, ip_1, available_2, ip_2, available_3],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def test_add_available_ipaddresses_ipv6(self):
|
|
50
|
+
namespace = Namespace.objects.create(name="add_available_ipv6")
|
|
51
|
+
prefix = Prefix.objects.create(
|
|
52
|
+
prefix="::/0", status=Status.objects.get_for_model(Prefix).first(), namespace=namespace
|
|
53
|
+
)
|
|
54
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
55
|
+
# .0 is available in IPv6
|
|
56
|
+
available_1 = (10, "::/0")
|
|
57
|
+
ip_1 = IPAddress.objects.create(address="::a/0", status=ip_status, namespace=namespace)
|
|
58
|
+
available_2 = (2**128 - 10 - 10 - 2, "::b/0")
|
|
59
|
+
ip_2 = IPAddress.objects.create(
|
|
60
|
+
address="ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff5/0", status=ip_status, namespace=namespace
|
|
61
|
+
)
|
|
62
|
+
available_3 = (10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff6/0")
|
|
63
|
+
self.assertEqual(
|
|
64
|
+
add_available_ipaddresses(prefix=netaddr.IPNetwork(prefix.prefix), ipaddress_list=(ip_1, ip_2)),
|
|
65
|
+
[available_1, ip_1, available_2, ip_2, available_3],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
30
69
|
class ParseNumericRangeTest(TestCase):
|
|
31
70
|
"""Tests for add_available_vlans()."""
|
|
32
71
|
|
nautobot/ipam/utils/__init__.py
CHANGED
|
@@ -41,20 +41,27 @@ def get_add_available_prefixes_callback(show_available: bool, parent: Prefix):
|
|
|
41
41
|
|
|
42
42
|
def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterable[IPAddress], is_pool: bool = False):
|
|
43
43
|
"""
|
|
44
|
-
Annotate ranges of available IP addresses within a given prefix.
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
Annotate ranges of available IP addresses within a given prefix.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
prefix (netaddr.IPNetwork): The network to calculate available addresses within.
|
|
48
|
+
ipaddress_list (Iterable[IPAddress]): List or QuerySet of extant IPAddress objects.
|
|
49
|
+
is_pool (bool): If True, the first/last IPs in the prefix will be considered usable, regardless of mask length.
|
|
47
50
|
|
|
51
|
+
Returns:
|
|
52
|
+
The contents of `ipaddress_list` interleaved with tuples of the form
|
|
53
|
+
`(number_of_available_addresses, first_such_address)`.
|
|
54
|
+
"""
|
|
48
55
|
output = []
|
|
49
56
|
prev_ip = None
|
|
50
57
|
|
|
51
58
|
# Ignore the network and broadcast addresses for non-pool IPv4 prefixes larger than /31.
|
|
52
59
|
if prefix.version == 4 and prefix.prefixlen < 31 and not is_pool:
|
|
53
|
-
first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1)
|
|
54
|
-
last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1)
|
|
60
|
+
first_ip_in_prefix = netaddr.IPAddress(prefix.first + 1, version=prefix.version)
|
|
61
|
+
last_ip_in_prefix = netaddr.IPAddress(prefix.last - 1, version=prefix.version)
|
|
55
62
|
else:
|
|
56
|
-
first_ip_in_prefix = netaddr.IPAddress(prefix.first)
|
|
57
|
-
last_ip_in_prefix = netaddr.IPAddress(prefix.last)
|
|
63
|
+
first_ip_in_prefix = netaddr.IPAddress(prefix.first, version=prefix.version)
|
|
64
|
+
last_ip_in_prefix = netaddr.IPAddress(prefix.last, version=prefix.version)
|
|
58
65
|
|
|
59
66
|
if not ipaddress_list:
|
|
60
67
|
return [
|
|
@@ -71,15 +78,15 @@ def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterabl
|
|
|
71
78
|
ipaddress_list.sort(key=lambda ip: ip.host)
|
|
72
79
|
|
|
73
80
|
# Account for any available IPs before the first real IP
|
|
74
|
-
if ipaddress_list[0].address.ip > first_ip_in_prefix:
|
|
75
|
-
skipped_count =
|
|
81
|
+
if ipaddress_list[0].address.ip.value > first_ip_in_prefix.value:
|
|
82
|
+
skipped_count = ipaddress_list[0].address.ip.value - first_ip_in_prefix.value
|
|
76
83
|
first_skipped = f"{first_ip_in_prefix}/{prefix.prefixlen}"
|
|
77
84
|
output.append((skipped_count, first_skipped))
|
|
78
85
|
|
|
79
86
|
# Iterate through existing IPs and annotate free ranges
|
|
80
87
|
for ip in ipaddress_list:
|
|
81
88
|
if prev_ip:
|
|
82
|
-
diff =
|
|
89
|
+
diff = ip.address.ip.value - prev_ip.address.ip.value
|
|
83
90
|
if diff > 1:
|
|
84
91
|
first_skipped = f"{prev_ip.address.ip + 1}/{prefix.prefixlen}"
|
|
85
92
|
output.append((diff - 1, first_skipped))
|
|
@@ -88,7 +95,7 @@ def add_available_ipaddresses(prefix: netaddr.IPNetwork, ipaddress_list: Iterabl
|
|
|
88
95
|
|
|
89
96
|
# Include any remaining available IPs
|
|
90
97
|
if prev_ip.address.ip < last_ip_in_prefix:
|
|
91
|
-
skipped_count =
|
|
98
|
+
skipped_count = last_ip_in_prefix.value - prev_ip.address.ip.value
|
|
92
99
|
first_skipped = f"{prev_ip.address.ip + 1}/{prefix.prefixlen}"
|
|
93
100
|
output.append((skipped_count, first_skipped))
|
|
94
101
|
|