nautobot 2.3.13__py3-none-any.whl → 2.3.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/circuits/tables.py +2 -1
- nautobot/core/filters.py +2 -0
- nautobot/core/jobs/cleanup.py +47 -11
- nautobot/core/settings.py +3 -0
- nautobot/core/settings.yaml +8 -0
- nautobot/core/templates/inc/media.html +3 -0
- nautobot/core/templates/nautobot_config.py.j2 +3 -0
- nautobot/core/templates/search.html +7 -0
- nautobot/core/testing/filters.py +20 -5
- nautobot/core/testing/mixins.py +7 -2
- nautobot/core/tests/integration/test_app_home.py +0 -1
- nautobot/core/tests/integration/test_app_navbar.py +0 -1
- nautobot/core/tests/integration/test_filters.py +0 -2
- nautobot/core/tests/integration/test_home.py +0 -1
- nautobot/core/tests/integration/test_navbar.py +0 -1
- nautobot/core/tests/integration/test_view_authentication.py +1 -0
- nautobot/core/tests/test_views.py +29 -0
- nautobot/core/urls.py +9 -0
- nautobot/dcim/forms.py +6 -5
- nautobot/dcim/models/devices.py +1 -0
- nautobot/dcim/tests/test_forms.py +51 -2
- nautobot/extras/forms/mixins.py +11 -3
- nautobot/extras/jobs.py +6 -4
- nautobot/extras/models/customfields.py +12 -11
- nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
- nautobot/extras/tests/test_forms.py +20 -1
- nautobot/ipam/api/views.py +17 -15
- nautobot/ipam/models.py +62 -11
- nautobot/ipam/tables.py +2 -2
- nautobot/ipam/tests/test_api.py +402 -2
- nautobot/ipam/tests/test_models.py +41 -0
- nautobot/project-static/docs/404.html +2 -2
- nautobot/project-static/docs/apps/index.html +2 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.88dd0f4e.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.88dd0f4e.min.js.map} +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +8 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +12 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
- nautobot/project-static/docs/development/apps/api/setup.html +2 -2
- nautobot/project-static/docs/development/apps/api/testing.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
- nautobot/project-static/docs/development/apps/index.html +2 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
- nautobot/project-static/docs/development/core/application-registry.html +2 -2
- nautobot/project-static/docs/development/core/best-practices.html +2 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
- nautobot/project-static/docs/development/core/caching.html +2 -2
- nautobot/project-static/docs/development/core/controllers.html +2 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +27 -70
- nautobot/project-static/docs/development/core/generic-views.html +2 -2
- nautobot/project-static/docs/development/core/getting-started.html +155 -141
- nautobot/project-static/docs/development/core/homepage.html +2 -2
- nautobot/project-static/docs/development/core/index.html +2 -2
- nautobot/project-static/docs/development/core/model-checklist.html +2 -2
- nautobot/project-static/docs/development/core/model-features.html +2 -2
- nautobot/project-static/docs/development/core/natural-keys.html +2 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
- nautobot/project-static/docs/development/core/release-checklist.html +2 -2
- nautobot/project-static/docs/development/core/role-internals.html +2 -2
- nautobot/project-static/docs/development/core/settings.html +2 -2
- nautobot/project-static/docs/development/core/style-guide.html +2 -2
- nautobot/project-static/docs/development/core/templates.html +2 -2
- nautobot/project-static/docs/development/core/testing.html +2 -2
- nautobot/project-static/docs/development/core/user-preferences.html +2 -2
- nautobot/project-static/docs/development/index.html +2 -2
- nautobot/project-static/docs/development/jobs/index.html +143 -119
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
- nautobot/project-static/docs/index.html +2 -2
- nautobot/project-static/docs/overview/application_stack.html +2 -2
- nautobot/project-static/docs/overview/design_philosophy.html +2 -2
- nautobot/project-static/docs/release-notes/index.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.3.html +536 -216
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +270 -270
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
- nautobot/project-static/docs/user-guide/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/METADATA +5 -4
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/RECORD +314 -315
- nautobot/core/fixtures/user-data.json +0 -59
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/NOTICE +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/WHEEL +0 -0
- {nautobot-2.3.13.dist-info → nautobot-2.3.15.dist-info}/entry_points.txt +0 -0
nautobot/ipam/tests/test_api.py
CHANGED
|
@@ -571,6 +571,126 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
571
571
|
self.assertIn("prefixcf", response.data[0]["custom_fields"])
|
|
572
572
|
self.assertEqual("value 1", response.data[0]["custom_fields"]["prefixcf"])
|
|
573
573
|
|
|
574
|
+
def test_create_available_prefixes_with_permissions_constraint(self):
|
|
575
|
+
# Prepare prefix and permissions
|
|
576
|
+
prefix = Prefix.objects.create(
|
|
577
|
+
prefix="10.2.3.0/24",
|
|
578
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
579
|
+
namespace=self.namespace,
|
|
580
|
+
status=self.status,
|
|
581
|
+
description="This is the Prefix created for whole network.",
|
|
582
|
+
)
|
|
583
|
+
url = reverse("ipam-api:prefix-available-prefixes", kwargs={"pk": prefix.pk})
|
|
584
|
+
self.add_permissions("ipam.view_prefix")
|
|
585
|
+
self.add_permissions(
|
|
586
|
+
"ipam.add_prefix", constraints={"description__startswith": "This is the Prefix created for"}
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
# Test invalid request
|
|
590
|
+
data = {
|
|
591
|
+
"prefix_length": 26,
|
|
592
|
+
"status": self.status.pk,
|
|
593
|
+
}
|
|
594
|
+
invalid_data_list = [
|
|
595
|
+
data,
|
|
596
|
+
{**data, "description": ""},
|
|
597
|
+
{**data, "description": "Some description"},
|
|
598
|
+
{**data, "description": "Some description. This is the IP created for"},
|
|
599
|
+
]
|
|
600
|
+
|
|
601
|
+
for invalid_data in invalid_data_list:
|
|
602
|
+
with self.subTest(case=invalid_data):
|
|
603
|
+
response = self.client.post(url, invalid_data, format="json", **self.header)
|
|
604
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
605
|
+
self.assertIn("detail", response.data)
|
|
606
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
607
|
+
|
|
608
|
+
# Verify that no prefixes were created (the entire prefix is still available)
|
|
609
|
+
response = self.client.get(url, **self.header)
|
|
610
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
611
|
+
self.assertEqual(len(response.data), 1)
|
|
612
|
+
self.assertEqual(response.data[0]["prefix"], prefix.cidr_str)
|
|
613
|
+
|
|
614
|
+
# Test valid request
|
|
615
|
+
valid_data = {
|
|
616
|
+
"prefix_length": 26,
|
|
617
|
+
"status": self.status.pk,
|
|
618
|
+
"description": "This is the Prefix created for my local network",
|
|
619
|
+
}
|
|
620
|
+
response = self.client.post(url, valid_data, format="json", **self.header)
|
|
621
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
622
|
+
self.assertEqual(response.data["prefix"], "10.2.3.0/26")
|
|
623
|
+
|
|
624
|
+
# Verify that prefix is created
|
|
625
|
+
response = self.client.get(url, **self.header)
|
|
626
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
627
|
+
self.assertEqual(len(response.data), 2)
|
|
628
|
+
self.assertEqual(response.data[0]["prefix"], "10.2.3.64/26")
|
|
629
|
+
self.assertEqual(response.data[1]["prefix"], "10.2.3.128/25")
|
|
630
|
+
|
|
631
|
+
def test_create_multiple_available_prefixes_with_permissions_constraint(self):
|
|
632
|
+
# Prepare prefix and permissions
|
|
633
|
+
prefix = Prefix.objects.create(
|
|
634
|
+
prefix="10.2.3.0/24",
|
|
635
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
636
|
+
namespace=self.namespace,
|
|
637
|
+
status=self.status,
|
|
638
|
+
description="This is the Prefix created for whole network.",
|
|
639
|
+
)
|
|
640
|
+
url = reverse("ipam-api:prefix-available-prefixes", kwargs={"pk": prefix.pk})
|
|
641
|
+
self.add_permissions("ipam.view_prefix")
|
|
642
|
+
self.add_permissions(
|
|
643
|
+
"ipam.add_prefix", constraints={"description__startswith": "This is the Prefix created for"}
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
# Test invalid request
|
|
647
|
+
data = [
|
|
648
|
+
{
|
|
649
|
+
"prefix_length": 26,
|
|
650
|
+
"status": self.status.pk,
|
|
651
|
+
"description": "This is the Prefix created for my local network",
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
"prefix_length": 26,
|
|
655
|
+
"status": self.status.pk,
|
|
656
|
+
},
|
|
657
|
+
]
|
|
658
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
659
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
660
|
+
self.assertIn("detail", response.data)
|
|
661
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
662
|
+
|
|
663
|
+
# Verify that no prefixes were created (the entire prefix is still available)
|
|
664
|
+
response = self.client.get(url, **self.header)
|
|
665
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
666
|
+
self.assertEqual(len(response.data), 1)
|
|
667
|
+
self.assertEqual(response.data[0]["prefix"], prefix.cidr_str)
|
|
668
|
+
|
|
669
|
+
# Test valid request
|
|
670
|
+
data = [
|
|
671
|
+
{
|
|
672
|
+
"prefix_length": 26,
|
|
673
|
+
"status": self.status.pk,
|
|
674
|
+
"description": "This is the Prefix created for my local network",
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
"prefix_length": 26,
|
|
678
|
+
"status": self.status.pk,
|
|
679
|
+
"description": "This is the Prefix created for my guest house network",
|
|
680
|
+
},
|
|
681
|
+
]
|
|
682
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
683
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
684
|
+
self.assertEqual(len(response.data), 2)
|
|
685
|
+
self.assertEqual(response.data[0]["prefix"], "10.2.3.0/26")
|
|
686
|
+
self.assertEqual(response.data[1]["prefix"], "10.2.3.64/26")
|
|
687
|
+
|
|
688
|
+
# Verify that prefixes were created
|
|
689
|
+
response = self.client.get(url, **self.header)
|
|
690
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
691
|
+
self.assertEqual(len(response.data), 1)
|
|
692
|
+
self.assertEqual(response.data[0]["prefix"], "10.2.3.128/25")
|
|
693
|
+
|
|
574
694
|
def test_list_available_ips(self):
|
|
575
695
|
"""
|
|
576
696
|
Test retrieval of all available IP addresses within a parent prefix.
|
|
@@ -594,9 +714,76 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
594
714
|
response = self.client.get(url, **self.header)
|
|
595
715
|
self.assertEqual(len(response.data), 6) # 8 - 2 because prefix.type = network
|
|
596
716
|
|
|
717
|
+
def test_list_available_ips_calculate_child_ips(self):
|
|
718
|
+
"""
|
|
719
|
+
Test retrieval of all available IP addresses when child IP's exists.
|
|
720
|
+
"""
|
|
721
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
722
|
+
prefix = Prefix.objects.create(
|
|
723
|
+
prefix="192.0.3.0/29",
|
|
724
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
725
|
+
namespace=self.namespace,
|
|
726
|
+
status=self.status,
|
|
727
|
+
)
|
|
728
|
+
Prefix.objects.create(
|
|
729
|
+
prefix="192.0.3.0/30",
|
|
730
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
731
|
+
namespace=self.namespace,
|
|
732
|
+
status=self.status,
|
|
733
|
+
)
|
|
734
|
+
IPAddress.objects.create(
|
|
735
|
+
address="192.0.3.1/30",
|
|
736
|
+
status=ip_status,
|
|
737
|
+
namespace=self.namespace,
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
741
|
+
self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
|
|
742
|
+
|
|
743
|
+
# Retrieve all available IPs
|
|
744
|
+
response = self.client.get(url, **self.header)
|
|
745
|
+
self.assertEqual(len(response.data), 7) # 7 because prefix.type = pool got 8 IP's minus one children IP
|
|
746
|
+
|
|
747
|
+
def test_create_single_available_ip_calculate_child_ips(self):
|
|
748
|
+
"""
|
|
749
|
+
Test creating a single IP when child IP's exists.
|
|
750
|
+
"""
|
|
751
|
+
ip_status = Status.objects.get_for_model(IPAddress).first()
|
|
752
|
+
prefix = Prefix.objects.create(
|
|
753
|
+
prefix="192.0.4.0/31",
|
|
754
|
+
namespace=self.namespace,
|
|
755
|
+
type=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
756
|
+
status=self.status,
|
|
757
|
+
)
|
|
758
|
+
Prefix.objects.create(
|
|
759
|
+
prefix="192.0.4.0/32",
|
|
760
|
+
type=choices.PrefixTypeChoices.TYPE_POOL,
|
|
761
|
+
namespace=self.namespace,
|
|
762
|
+
status=self.status,
|
|
763
|
+
)
|
|
764
|
+
IPAddress.objects.create(
|
|
765
|
+
address="192.0.4.0/32",
|
|
766
|
+
status=ip_status,
|
|
767
|
+
namespace=self.namespace,
|
|
768
|
+
)
|
|
769
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
770
|
+
self.add_permissions("ipam.view_prefix", "ipam.add_ipaddress", "extras.view_status")
|
|
771
|
+
|
|
772
|
+
data = {
|
|
773
|
+
"status": self.status.pk,
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
777
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
778
|
+
self.assertEqual(str(response.data["parent"]["url"]), self.absolute_api_url(prefix))
|
|
779
|
+
|
|
780
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
781
|
+
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
|
|
782
|
+
self.assertIn("detail", response.data)
|
|
783
|
+
|
|
597
784
|
def test_create_single_available_ip(self):
|
|
598
785
|
"""
|
|
599
|
-
Test
|
|
786
|
+
Test creating single IP will return 204 No content when pool is fully filled.
|
|
600
787
|
"""
|
|
601
788
|
prefix = Prefix.objects.create(
|
|
602
789
|
prefix="192.0.2.0/29",
|
|
@@ -664,6 +851,116 @@ class PrefixTest(APIViewTestCases.APIViewTestCase):
|
|
|
664
851
|
self.assertIn("ipcf", response.data[0]["custom_fields"])
|
|
665
852
|
self.assertEqual("1", response.data[0]["custom_fields"]["ipcf"])
|
|
666
853
|
|
|
854
|
+
def test_create_available_ips_with_permissions_constraint(self):
|
|
855
|
+
# Prepare prefix and permissions
|
|
856
|
+
prefix = Prefix.objects.create(
|
|
857
|
+
prefix="192.168.0.0/30",
|
|
858
|
+
type=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
859
|
+
namespace=self.namespace,
|
|
860
|
+
status=self.status,
|
|
861
|
+
description="This is the Prefix created for whole network.",
|
|
862
|
+
)
|
|
863
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
864
|
+
self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
|
|
865
|
+
self.add_permissions(
|
|
866
|
+
"ipam.add_ipaddress", constraints={"description__startswith": "This is the IP created for"}
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
# Test invalid request
|
|
870
|
+
data = {
|
|
871
|
+
"status": self.status.pk,
|
|
872
|
+
}
|
|
873
|
+
invalid_data_list = [
|
|
874
|
+
data,
|
|
875
|
+
{**data, "description": ""},
|
|
876
|
+
{**data, "description": "Some description"},
|
|
877
|
+
{**data, "description": "Some description. This is the IP created for"},
|
|
878
|
+
]
|
|
879
|
+
|
|
880
|
+
for invalid_data in invalid_data_list:
|
|
881
|
+
with self.subTest(case=invalid_data):
|
|
882
|
+
response = self.client.post(url, invalid_data, format="json", **self.header)
|
|
883
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
884
|
+
self.assertIn("detail", response.data)
|
|
885
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
886
|
+
|
|
887
|
+
# Verify that no IPs were created (the entire prefix pool is still available)
|
|
888
|
+
response = self.client.get(url, **self.header)
|
|
889
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
890
|
+
self.assertEqual(len(response.data), 2)
|
|
891
|
+
|
|
892
|
+
# Test valid request
|
|
893
|
+
valid_data = {
|
|
894
|
+
"status": self.status.pk,
|
|
895
|
+
"description": "This is the IP created for my private laptop",
|
|
896
|
+
}
|
|
897
|
+
response = self.client.post(url, valid_data, format="json", **self.header)
|
|
898
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
899
|
+
self.assertEqual(response.data["address"], "192.168.0.1/30")
|
|
900
|
+
|
|
901
|
+
# Verify that IP is created
|
|
902
|
+
response = self.client.get(url, **self.header)
|
|
903
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
904
|
+
self.assertEqual(len(response.data), 1)
|
|
905
|
+
self.assertEqual(response.data[0]["address"], "192.168.0.2/30")
|
|
906
|
+
|
|
907
|
+
def test_create_multiple_available_ips_with_permissions_constraint(self):
|
|
908
|
+
# Prepare prefix and permissions
|
|
909
|
+
prefix = Prefix.objects.create(
|
|
910
|
+
prefix="192.168.0.0/30",
|
|
911
|
+
type=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
912
|
+
namespace=self.namespace,
|
|
913
|
+
status=self.status,
|
|
914
|
+
description="This is a Prefix created for whole network.",
|
|
915
|
+
)
|
|
916
|
+
url = reverse("ipam-api:prefix-available-ips", kwargs={"pk": prefix.pk})
|
|
917
|
+
self.add_permissions("ipam.view_prefix", "ipam.view_ipaddress")
|
|
918
|
+
self.add_permissions(
|
|
919
|
+
"ipam.add_ipaddress", constraints={"description__startswith": "This is the IP created for"}
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
# Test invalid request
|
|
923
|
+
data = [
|
|
924
|
+
{
|
|
925
|
+
"status": self.status.pk,
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
"status": self.status.pk,
|
|
929
|
+
"description": "This is an IP created for my private laptop",
|
|
930
|
+
},
|
|
931
|
+
]
|
|
932
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
933
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
934
|
+
self.assertIn("detail", response.data)
|
|
935
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
936
|
+
|
|
937
|
+
# Verify that no IPs were created (the entire prefix pool is still available)
|
|
938
|
+
response = self.client.get(url, **self.header)
|
|
939
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
940
|
+
self.assertEqual(len(response.data), 2)
|
|
941
|
+
|
|
942
|
+
# Test valid request
|
|
943
|
+
valid_data = [
|
|
944
|
+
{
|
|
945
|
+
"status": self.status.pk,
|
|
946
|
+
"description": "This is the IP created for my private laptop",
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
"status": self.status.pk,
|
|
950
|
+
"description": "This is the IP created for my gaming laptop",
|
|
951
|
+
},
|
|
952
|
+
]
|
|
953
|
+
response = self.client.post(url, valid_data, format="json", **self.header)
|
|
954
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
955
|
+
self.assertEqual(len(response.data), 2)
|
|
956
|
+
self.assertEqual(response.data[0]["address"], "192.168.0.1/30")
|
|
957
|
+
self.assertEqual(response.data[1]["address"], "192.168.0.2/30")
|
|
958
|
+
|
|
959
|
+
# Verify that IPs are created
|
|
960
|
+
response = self.client.get(url, **self.header)
|
|
961
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
962
|
+
self.assertEqual(len(response.data), 0)
|
|
963
|
+
|
|
667
964
|
|
|
668
965
|
class PrefixLocationAssignmentTest(APIViewTestCases.APIViewTestCase):
|
|
669
966
|
model = PrefixLocationAssignment
|
|
@@ -1022,7 +1319,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1022
1319
|
Test retrieval of all available VLAN IDs within a VLANGroup.
|
|
1023
1320
|
"""
|
|
1024
1321
|
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1025
|
-
self.add_permissions("ipam.view_vlangroup")
|
|
1322
|
+
self.add_permissions("ipam.view_vlangroup", "ipam.view_vlan")
|
|
1026
1323
|
|
|
1027
1324
|
# Retrieve all available VLAN IDs
|
|
1028
1325
|
response = self.client.get(url, **self.header)
|
|
@@ -1039,6 +1336,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1039
1336
|
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1040
1337
|
self.add_permissions(
|
|
1041
1338
|
"ipam.view_vlangroup",
|
|
1339
|
+
"ipam.view_vlan",
|
|
1042
1340
|
"ipam.add_vlan",
|
|
1043
1341
|
)
|
|
1044
1342
|
|
|
@@ -1080,6 +1378,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1080
1378
|
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1081
1379
|
self.add_permissions(
|
|
1082
1380
|
"ipam.view_vlangroup",
|
|
1381
|
+
"ipam.view_vlan",
|
|
1083
1382
|
"ipam.add_vlan",
|
|
1084
1383
|
)
|
|
1085
1384
|
|
|
@@ -1126,6 +1425,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1126
1425
|
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1127
1426
|
self.add_permissions(
|
|
1128
1427
|
"ipam.view_vlangroup",
|
|
1428
|
+
"ipam.view_vlan",
|
|
1129
1429
|
"ipam.add_vlan",
|
|
1130
1430
|
)
|
|
1131
1431
|
|
|
@@ -1155,6 +1455,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1155
1455
|
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1156
1456
|
self.add_permissions(
|
|
1157
1457
|
"ipam.view_vlangroup",
|
|
1458
|
+
"ipam.view_vlan",
|
|
1158
1459
|
"ipam.add_vlan",
|
|
1159
1460
|
)
|
|
1160
1461
|
|
|
@@ -1182,6 +1483,105 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|
|
1182
1483
|
response.data["detail"],
|
|
1183
1484
|
)
|
|
1184
1485
|
|
|
1486
|
+
def test_create_available_vlans_with_permissions_constraint(self):
|
|
1487
|
+
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1488
|
+
self.add_permissions(
|
|
1489
|
+
"ipam.view_vlangroup",
|
|
1490
|
+
"ipam.view_vlan",
|
|
1491
|
+
)
|
|
1492
|
+
self.add_permissions("ipam.add_vlan", constraints={"description__startswith": "This is the VLAN created for"})
|
|
1493
|
+
|
|
1494
|
+
data = {"name": "VLAN_6", "status": self.default_status.pk, "vid": 6}
|
|
1495
|
+
invalid_data_list = [
|
|
1496
|
+
data,
|
|
1497
|
+
{**data, "description": ""},
|
|
1498
|
+
{**data, "description": "Some description"},
|
|
1499
|
+
{**data, "description": "Some description. This is the VLAN created for"},
|
|
1500
|
+
]
|
|
1501
|
+
|
|
1502
|
+
# Test invalid request
|
|
1503
|
+
for invalid_data in invalid_data_list:
|
|
1504
|
+
with self.subTest(case=invalid_data):
|
|
1505
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1506
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
1507
|
+
self.assertIn("detail", response.data)
|
|
1508
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
1509
|
+
|
|
1510
|
+
# Verify that no VLANs were created (number of VLANs is the same as on the beginning of the test)
|
|
1511
|
+
response = self.client.get(url, **self.header)
|
|
1512
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1513
|
+
self.assertEqual(len(response.data["results"]), len(self.unused_vids))
|
|
1514
|
+
|
|
1515
|
+
# Test valid request
|
|
1516
|
+
valid_data = {
|
|
1517
|
+
"name": "VLAN_6",
|
|
1518
|
+
"status": self.default_status.pk,
|
|
1519
|
+
"vid": 6,
|
|
1520
|
+
"description": "This is the VLAN created for home automation.",
|
|
1521
|
+
}
|
|
1522
|
+
response = self.client.post(url, valid_data, format="json", **self.header)
|
|
1523
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
1524
|
+
self.assertEqual(response.data["results"]["name"], valid_data["name"])
|
|
1525
|
+
|
|
1526
|
+
# Verify that VLAN is created
|
|
1527
|
+
response = self.client.get(url, **self.header)
|
|
1528
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1529
|
+
self.assertEqual(
|
|
1530
|
+
len(response.data["results"]), len(self.unused_vids) - 1
|
|
1531
|
+
) # initial unsued vids minus one created
|
|
1532
|
+
|
|
1533
|
+
def test_create_multiple_available_vlans_with_permissions_constraint(self):
|
|
1534
|
+
url = reverse("ipam-api:vlangroup-available-vlans", kwargs={"pk": self.vlan_group.pk})
|
|
1535
|
+
self.add_permissions(
|
|
1536
|
+
"ipam.view_vlangroup",
|
|
1537
|
+
"ipam.view_vlan",
|
|
1538
|
+
)
|
|
1539
|
+
self.add_permissions("ipam.add_vlan", constraints={"description__startswith": "This is the VLAN created for"})
|
|
1540
|
+
|
|
1541
|
+
# Test invalid request
|
|
1542
|
+
data = [
|
|
1543
|
+
{"name": "VLAN_6", "status": self.default_status.pk},
|
|
1544
|
+
{"name": "VLAN_7", "status": self.default_status.pk},
|
|
1545
|
+
{"name": "VLAN_8", "status": self.default_status.pk},
|
|
1546
|
+
]
|
|
1547
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
1548
|
+
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
1549
|
+
self.assertIn("detail", response.data)
|
|
1550
|
+
self.assertEqual(response.data["detail"], "You do not have permission to perform this action.")
|
|
1551
|
+
|
|
1552
|
+
# Verify that no VLANs were created (number of VLANs is the same as on the beginning of the test)
|
|
1553
|
+
response = self.client.get(url, **self.header)
|
|
1554
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1555
|
+
self.assertEqual(len(response.data["results"]), len(self.unused_vids))
|
|
1556
|
+
|
|
1557
|
+
# Test valid request
|
|
1558
|
+
valid_data = [
|
|
1559
|
+
{
|
|
1560
|
+
"name": "VLAN_6",
|
|
1561
|
+
"status": self.default_status.pk,
|
|
1562
|
+
"description": "This is the VLAN created for home automation.",
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
"name": "VLAN_7",
|
|
1566
|
+
"status": self.default_status.pk,
|
|
1567
|
+
"description": "This is the VLAN created for IP cameras.",
|
|
1568
|
+
},
|
|
1569
|
+
{"name": "VLAN_8", "status": self.default_status.pk, "description": "This is the VLAN created for guests."},
|
|
1570
|
+
]
|
|
1571
|
+
response = self.client.post(url, valid_data, format="json", **self.header)
|
|
1572
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
1573
|
+
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
1574
|
+
self.assertEqual(len(response.data["results"]), 3)
|
|
1575
|
+
for i, vlan_data in enumerate(data):
|
|
1576
|
+
self.assertEqual(response.data["results"][i]["name"], vlan_data["name"])
|
|
1577
|
+
|
|
1578
|
+
# Verify that VLANs are created
|
|
1579
|
+
response = self.client.get(url, **self.header)
|
|
1580
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
1581
|
+
self.assertEqual(
|
|
1582
|
+
len(response.data["results"]), len(self.unused_vids) - 3
|
|
1583
|
+
) # initial unsued vids minus three created
|
|
1584
|
+
|
|
1185
1585
|
|
|
1186
1586
|
class VLANTest(APIViewTestCases.APIViewTestCase):
|
|
1187
1587
|
model = VLAN
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from unittest import skipIf
|
|
2
|
+
from unittest.mock import patch
|
|
2
3
|
|
|
3
4
|
from django.contrib.contenttypes.models import ContentType
|
|
4
5
|
from django.core.exceptions import ValidationError
|
|
@@ -413,6 +414,39 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
413
414
|
self.child1 = Prefix.objects.create(prefix="101.102.0.0/26", status=self.status, namespace=self.namespace)
|
|
414
415
|
self.child2 = Prefix.objects.create(prefix="101.102.0.64/26", status=self.status, namespace=self.namespace)
|
|
415
416
|
|
|
417
|
+
def test_parent_exists_after_model_clean(self):
|
|
418
|
+
prefix = Prefix(
|
|
419
|
+
prefix="101.102.0.2/26", status=self.status, namespace=self.namespace, type=PrefixTypeChoices.TYPE_CONTAINER
|
|
420
|
+
)
|
|
421
|
+
prefix.clean()
|
|
422
|
+
self.assertEqual(prefix.parent, self.child1)
|
|
423
|
+
|
|
424
|
+
def test_reparent_subnets_and_reparent_ips_call(self):
|
|
425
|
+
"""Assert reparent_subnets and reparent_ips are only called if there is an update to either of network, namespace or prefix_length"""
|
|
426
|
+
prefix_ip = "101.102.0.0/28"
|
|
427
|
+
with self.subTest("Assert reparent_subnets"):
|
|
428
|
+
with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
|
|
429
|
+
Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
|
|
430
|
+
mock_reparent_subnets.assert_called_once()
|
|
431
|
+
|
|
432
|
+
with patch.object(Prefix, "reparent_subnets", return_value=None) as mock_reparent_subnets:
|
|
433
|
+
prefix = Prefix.objects.get(prefix=prefix_ip)
|
|
434
|
+
prefix.description = "Sample Description"
|
|
435
|
+
prefix.save()
|
|
436
|
+
mock_reparent_subnets.assert_not_called()
|
|
437
|
+
prefix.delete()
|
|
438
|
+
|
|
439
|
+
with self.subTest("Assert reparent_ips"):
|
|
440
|
+
with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
|
|
441
|
+
Prefix.objects.create(prefix=prefix_ip, status=self.status, namespace=self.namespace)
|
|
442
|
+
reparent_ips.assert_called_once()
|
|
443
|
+
|
|
444
|
+
with patch.object(Prefix, "reparent_ips", return_value=None) as reparent_ips:
|
|
445
|
+
prefix = Prefix.objects.get(prefix=prefix_ip)
|
|
446
|
+
prefix.description = "Sample Description"
|
|
447
|
+
prefix.save()
|
|
448
|
+
reparent_ips.assert_not_called()
|
|
449
|
+
|
|
416
450
|
def test_location_queries(self):
|
|
417
451
|
locations = Location.objects.all()[:4]
|
|
418
452
|
for location in locations:
|
|
@@ -766,6 +800,13 @@ class TestPrefix(ModelTestCases.BaseModelTestCase):
|
|
|
766
800
|
IPAddress.objects.create(address="10.0.0.4/24", status=self.status, namespace=self.namespace)
|
|
767
801
|
self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.0.5/24")
|
|
768
802
|
|
|
803
|
+
def test_get_first_available_ip_calculate_child_ips(self):
|
|
804
|
+
parent_prefix = Prefix.objects.create(prefix="10.0.3.0/29", status=self.status, namespace=self.namespace)
|
|
805
|
+
Prefix.objects.create(prefix="10.0.3.0/30", status=self.status, namespace=self.namespace)
|
|
806
|
+
IPAddress(address="10.0.3.1/30", status=self.status, namespace=self.namespace).save()
|
|
807
|
+
|
|
808
|
+
self.assertEqual(parent_prefix.get_first_available_ip(), "10.0.3.2/29")
|
|
809
|
+
|
|
769
810
|
def test_get_utilization(self):
|
|
770
811
|
# Container Prefix
|
|
771
812
|
prefix = Prefix.objects.create(
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
<link rel="icon" href="/projects/core/en/stable/assets/favicon.ico">
|
|
15
|
-
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.
|
|
15
|
+
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
@@ -8650,7 +8650,7 @@
|
|
|
8650
8650
|
<script id="__config" type="application/json">{"base": "/projects/core/en/stable/", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "/projects/core/en/stable/assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
|
8651
8651
|
|
|
8652
8652
|
|
|
8653
|
-
<script src="/projects/core/en/stable/assets/javascripts/bundle.
|
|
8653
|
+
<script src="/projects/core/en/stable/assets/javascripts/bundle.88dd0f4e.min.js"></script>
|
|
8654
8654
|
|
|
8655
8655
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
8656
8656
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
<link rel="icon" href="../assets/favicon.ico">
|
|
21
|
-
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.
|
|
21
|
+
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
@@ -8817,7 +8817,7 @@
|
|
|
8817
8817
|
<script id="__config" type="application/json">{"base": "..", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
|
8818
8818
|
|
|
8819
8819
|
|
|
8820
|
-
<script src="../assets/javascripts/bundle.
|
|
8820
|
+
<script src="../assets/javascripts/bundle.88dd0f4e.min.js"></script>
|
|
8821
8821
|
|
|
8822
8822
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
8823
8823
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
<link rel="icon" href="../assets/favicon.ico">
|
|
19
|
-
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.
|
|
19
|
+
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.5.49">
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
@@ -8751,7 +8751,7 @@
|
|
|
8751
8751
|
<script id="__config" type="application/json">{"base": "..", "features": ["content.code.copy", "content.tabs.link", "navigation.footer", "navigation.tabs", "navigation.tabs.sticky", "navigation.tracking", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.6ce7567c.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
|
8752
8752
|
|
|
8753
8753
|
|
|
8754
|
-
<script src="../assets/javascripts/bundle.
|
|
8754
|
+
<script src="../assets/javascripts/bundle.88dd0f4e.min.js"></script>
|
|
8755
8755
|
|
|
8756
8756
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
8757
8757
|
|