nautobot 3.0.3__py3-none-any.whl → 3.0.5__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.
- nautobot/core/authentication.py +0 -1
- nautobot/core/celery/schedulers.py +1 -3
- nautobot/core/cli/__init__.py +81 -39
- nautobot/core/settings.yaml +12 -4
- nautobot/core/tables.py +28 -17
- nautobot/core/templates/graphene/graphiql.html +3 -5
- nautobot/core/templates/inc/javascript.html +5 -10
- nautobot/core/templates/inc/media.html +5 -4
- nautobot/core/templates/inc/media_failure.html +73 -0
- nautobot/core/templates/media_failure.html +1 -0
- nautobot/core/tests/test_cli.py +120 -1
- nautobot/core/tests/test_templatetags_helpers.py +9 -9
- nautobot/core/ui/object_detail.py +1 -0
- nautobot/dcim/forms.py +1 -0
- nautobot/dcim/tables/devices.py +6 -5
- nautobot/dcim/tables/template_code.py +8 -4
- nautobot/dcim/templates/dcim/platform_create.html +3 -4
- nautobot/dcim/tests/test_tables.py +5 -6
- nautobot/dcim/views.py +6 -7
- nautobot/extras/models/jobs.py +7 -1
- nautobot/extras/signals.py +143 -113
- nautobot/extras/tables.py +3 -3
- nautobot/extras/templates/extras/inc/jobresult_js.html +1 -2
- nautobot/extras/templates/extras/scheduledjob.html +3 -1
- nautobot/extras/tests/test_customfields.py +75 -9
- nautobot/extras/tests/test_utils.py +116 -1
- nautobot/extras/utils.py +18 -16
- nautobot/extras/views.py +2 -14
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/filters.py +58 -3
- nautobot/ipam/tables.py +8 -4
- nautobot/ipam/tests/test_filters.py +55 -0
- nautobot/project-static/dist/css/nautobot.css +1 -1
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/docs/404.html +64 -8
- nautobot/project-static/docs/apps/index.html +64 -8
- nautobot/project-static/docs/apps/nautobot-apps.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/templatetags.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +64 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +64 -8
- nautobot/project-static/docs/development/apps/api/configuration-view.html +64 -8
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +64 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +67 -11
- nautobot/project-static/docs/development/apps/api/models/global-search.html +64 -8
- nautobot/project-static/docs/development/apps/api/models/graphql.html +64 -8
- nautobot/project-static/docs/development/apps/api/models/index.html +64 -8
- nautobot/project-static/docs/development/apps/api/models/queryset.html +13440 -0
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +64 -8
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +64 -8
- nautobot/project-static/docs/development/apps/api/prometheus.html +64 -8
- nautobot/project-static/docs/development/apps/api/setup.html +64 -8
- nautobot/project-static/docs/development/apps/api/testing.html +64 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +64 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +64 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +64 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +64 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/base-template.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/index.html +67 -11
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/notes.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +64 -8
- nautobot/project-static/docs/development/apps/api/views/urls.html +64 -8
- nautobot/project-static/docs/development/apps/index.html +64 -8
- nautobot/project-static/docs/development/apps/migration/code-updates.html +64 -8
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +64 -8
- nautobot/project-static/docs/development/apps/migration/from-v1.html +64 -8
- nautobot/project-static/docs/development/apps/migration/from-v2/migrating-v2-to-v3.html +64 -8
- nautobot/project-static/docs/development/apps/migration/from-v2/new-nautobot-custom-ui-apis.html +64 -8
- nautobot/project-static/docs/development/apps/migration/from-v2/overview.html +68 -8
- nautobot/project-static/docs/development/apps/migration/from-v2/upgrading-from-bootstrap-v3-to-v5.html +64 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +64 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +64 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +64 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +64 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +64 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +64 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +64 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +64 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +64 -8
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +64 -8
- nautobot/project-static/docs/development/core/application-registry.html +64 -8
- nautobot/project-static/docs/development/core/best-practices.html +64 -8
- nautobot/project-static/docs/development/core/caching.html +64 -8
- nautobot/project-static/docs/development/core/controllers.html +64 -8
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +64 -8
- nautobot/project-static/docs/development/core/docs-media-standards.html +64 -8
- nautobot/project-static/docs/development/core/generic-views.html +64 -8
- nautobot/project-static/docs/development/core/getting-started.html +64 -8
- nautobot/project-static/docs/development/core/homepage.html +64 -8
- nautobot/project-static/docs/development/core/index.html +64 -8
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +64 -8
- nautobot/project-static/docs/development/core/model-checklist.html +64 -8
- nautobot/project-static/docs/development/core/model-features.html +64 -8
- nautobot/project-static/docs/development/core/natural-keys.html +64 -8
- nautobot/project-static/docs/development/core/navigation-menu.html +64 -8
- nautobot/project-static/docs/development/core/release-checklist.html +66 -8
- nautobot/project-static/docs/development/core/role-internals.html +64 -8
- nautobot/project-static/docs/development/core/settings.html +64 -8
- nautobot/project-static/docs/development/core/style-guide.html +64 -8
- nautobot/project-static/docs/development/core/templates.html +64 -8
- nautobot/project-static/docs/development/core/testing.html +64 -8
- nautobot/project-static/docs/development/core/ui-best-practices.html +64 -8
- nautobot/project-static/docs/development/core/ui-component-framework.html +64 -8
- nautobot/project-static/docs/development/core/user-preferences.html +64 -8
- nautobot/project-static/docs/development/index.html +64 -8
- nautobot/project-static/docs/development/jobs/getting-started.html +64 -8
- nautobot/project-static/docs/development/jobs/index.html +64 -8
- nautobot/project-static/docs/development/jobs/installation.html +64 -8
- nautobot/project-static/docs/development/jobs/job-extensions.html +64 -8
- nautobot/project-static/docs/development/jobs/job-logging.html +64 -8
- nautobot/project-static/docs/development/jobs/job-patterns.html +64 -8
- nautobot/project-static/docs/development/jobs/job-structure.html +64 -8
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +64 -8
- nautobot/project-static/docs/development/jobs/testing.html +64 -8
- nautobot/project-static/docs/index.html +64 -8
- nautobot/project-static/docs/overview/application_stack.html +64 -8
- nautobot/project-static/docs/overview/design_philosophy.html +64 -8
- nautobot/project-static/docs/release-notes/index.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.0.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.1.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.2.html +65 -9
- nautobot/project-static/docs/release-notes/version-1.3.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.4.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.5.html +64 -8
- nautobot/project-static/docs/release-notes/version-1.6.html +64 -8
- nautobot/project-static/docs/release-notes/version-2.0.html +64 -8
- nautobot/project-static/docs/release-notes/version-2.1.html +64 -8
- nautobot/project-static/docs/release-notes/version-2.2.html +64 -8
- nautobot/project-static/docs/release-notes/version-2.3.html +64 -8
- nautobot/project-static/docs/release-notes/version-2.4.html +584 -8
- nautobot/project-static/docs/release-notes/version-3.0.html +467 -8
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +337 -329
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +64 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +64 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +64 -8
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +64 -8
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +64 -8
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +75 -12
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +64 -8
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +72 -9
- nautobot/project-static/docs/user-guide/administration/installation/index.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +64 -8
- nautobot/project-static/docs/user-guide/administration/installation/services.html +64 -8
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +65 -9
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +64 -8
- nautobot/project-static/docs/user-guide/administration/security/index.html +64 -8
- nautobot/project-static/docs/user-guide/administration/security/notices.html +64 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +64 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +67 -11
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +64 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v2/index.html +68 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/postgresql.html +13391 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +125 -15
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/certificateprofile.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/healthcheckmonitor.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/index.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpool.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/loadbalancerpoolmember.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/load-balancers/virtualserver.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/index.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpn.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase1policy.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnphase2policy.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpnprofile.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnel.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/vpn/vpntunnelendpoint.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +64 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/data-compliance.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +75 -12
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +85 -17
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-dark.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2-light.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/load-balancers.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +64 -8
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +64 -8
- nautobot/project-static/docs/user-guide/index.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/approval-workflow.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/data-validation.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/echarts.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +116 -34
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/user-interface/configurablecolumns.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/user-interface/savedview.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/user-interface/search.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/groups.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +64 -8
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +64 -8
- nautobot/tenancy/tables.py +1 -1
- nautobot/ui/package-lock.json +36 -36
- nautobot/ui/package.json +3 -3
- nautobot/ui/src/scss/nautobot.scss +2 -1
- nautobot/users/models.py +33 -0
- nautobot/users/tests/test_models.py +83 -0
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/METADATA +4 -4
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/RECORD +397 -386
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/12-add-tenant.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/13-assign-tenant-to-device.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/14-assign-tenant-to-device-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/22-create-vlans.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/23-create-vlans-2.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/24-vlan-main-page.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/25-add-vlan-to-interface.png +0 -0
- nautobot/project-static/docs/user-guide/feature-guides/images/getting-started-nautobot-ui/26-add-vlan-to-interface-2.png +0 -0
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/NOTICE +0 -0
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/WHEEL +0 -0
- {nautobot-3.0.3.dist-info → nautobot-3.0.5.dist-info}/entry_points.txt +0 -0
nautobot/extras/tables.py
CHANGED
|
@@ -374,11 +374,11 @@ class ApprovalWorkflowStageTable(BaseTable):
|
|
|
374
374
|
actions_needed = tables.TemplateColumn(
|
|
375
375
|
template_code="""
|
|
376
376
|
{% if record.remaining_approvals == 1 %}
|
|
377
|
-
|
|
377
|
+
{{ record.remaining_approvals }} more approval needed
|
|
378
378
|
{% elif record.remaining_approvals == 0 %}
|
|
379
|
-
|
|
379
|
+
<span class="text-secondary">—</span>
|
|
380
380
|
{% else %}
|
|
381
|
-
|
|
381
|
+
{{ record.remaining_approvals }} more approvals needed
|
|
382
382
|
{% endif %}
|
|
383
383
|
""",
|
|
384
384
|
orderable=False,
|
|
@@ -10,5 +10,4 @@
|
|
|
10
10
|
var job_result_id = "{{ result.pk }}";
|
|
11
11
|
|
|
12
12
|
</script>
|
|
13
|
-
<script src="{% versioned_static 'js/job_result.js' %}"
|
|
14
|
-
onerror="window.location='{% url 'media_failure' %}?filename=js/job_result.js'"></script>
|
|
13
|
+
<script src="{% versioned_static 'js/job_result.js' %}" onerror="nb.media.handleFailure(this)"></script>
|
|
@@ -143,7 +143,9 @@
|
|
|
143
143
|
<td>{% if value is None %}–{% else %}<code>{{ value }}</code>{% endif %}</td>
|
|
144
144
|
</tr>
|
|
145
145
|
{% empty %}
|
|
146
|
-
<tr
|
|
146
|
+
<tr>
|
|
147
|
+
<td><span class="text-secondary">—</span></td>
|
|
148
|
+
</tr>
|
|
147
149
|
{% endfor %}
|
|
148
150
|
</table>
|
|
149
151
|
</div>
|
|
@@ -2338,6 +2338,22 @@ class CustomFieldTableTest(TestCase):
|
|
|
2338
2338
|
cf_multi_select.default = ["Foo", "Bar"]
|
|
2339
2339
|
cf_multi_select.validated_save()
|
|
2340
2340
|
|
|
2341
|
+
# JSON custom field
|
|
2342
|
+
cf_json = CustomField(
|
|
2343
|
+
type=CustomFieldTypeChoices.TYPE_JSON,
|
|
2344
|
+
label="JSON Field",
|
|
2345
|
+
)
|
|
2346
|
+
cf_json.validated_save()
|
|
2347
|
+
cf_json.content_types.set([content_type])
|
|
2348
|
+
|
|
2349
|
+
# Markdown custom field
|
|
2350
|
+
cf_markdown = CustomField(
|
|
2351
|
+
type=CustomFieldTypeChoices.TYPE_MARKDOWN,
|
|
2352
|
+
label="Markdown Field",
|
|
2353
|
+
)
|
|
2354
|
+
cf_markdown.validated_save()
|
|
2355
|
+
cf_markdown.content_types.set([content_type])
|
|
2356
|
+
|
|
2341
2357
|
statuses = Status.objects.get_for_model(Location)
|
|
2342
2358
|
|
|
2343
2359
|
# Create a location
|
|
@@ -2346,7 +2362,7 @@ class CustomFieldTableTest(TestCase):
|
|
|
2346
2362
|
name="Location Custom", status=statuses.first(), location_type=location_type
|
|
2347
2363
|
)
|
|
2348
2364
|
|
|
2349
|
-
# Assign custom field values for location
|
|
2365
|
+
# Assign custom field values for location
|
|
2350
2366
|
self.location._custom_field_data = {
|
|
2351
2367
|
cf_text.key: "bar",
|
|
2352
2368
|
cf_integer.key: 456,
|
|
@@ -2355,16 +2371,39 @@ class CustomFieldTableTest(TestCase):
|
|
|
2355
2371
|
cf_url.key: "http://example.com/2",
|
|
2356
2372
|
cf_select.key: "Bar",
|
|
2357
2373
|
cf_multi_select.key: ["Bar", "Baz"],
|
|
2374
|
+
cf_json.key: {"hello": "world"},
|
|
2375
|
+
cf_markdown.key: "## Heading",
|
|
2358
2376
|
}
|
|
2359
2377
|
self.location.validated_save()
|
|
2360
2378
|
|
|
2379
|
+
# Create a second location
|
|
2380
|
+
self.location_2 = Location.objects.create(
|
|
2381
|
+
name="Location Custom 2", status=statuses.first(), location_type=location_type
|
|
2382
|
+
)
|
|
2383
|
+
|
|
2384
|
+
# Assign custom field values for location 2
|
|
2385
|
+
self.location_2._custom_field_data = {
|
|
2386
|
+
cf_text.key: "<script></script>",
|
|
2387
|
+
cf_integer.key: 0,
|
|
2388
|
+
cf_boolean.key: False,
|
|
2389
|
+
cf_date.key: None,
|
|
2390
|
+
cf_url.key: "",
|
|
2391
|
+
cf_select.key: None,
|
|
2392
|
+
cf_multi_select.key: [],
|
|
2393
|
+
cf_json.key: {},
|
|
2394
|
+
cf_markdown.key: "",
|
|
2395
|
+
}
|
|
2396
|
+
self.location_2.validated_save()
|
|
2397
|
+
|
|
2398
|
+
self.maxDiff = None
|
|
2399
|
+
|
|
2361
2400
|
def test_custom_field_table_render(self):
|
|
2362
|
-
queryset = Location.objects.filter(
|
|
2401
|
+
queryset = Location.objects.filter(name__in=[self.location.name, self.location_2.name])
|
|
2363
2402
|
location_table = LocationTable(queryset)
|
|
2364
2403
|
|
|
2365
2404
|
custom_column_expected = {
|
|
2366
2405
|
"text_field": "bar",
|
|
2367
|
-
"number_field":
|
|
2406
|
+
"number_field": 456,
|
|
2368
2407
|
"boolean_field": '<span class="text-success"><i class="mdi mdi-check-bold" title="Yes"></i></span>',
|
|
2369
2408
|
"date_field": "2020-01-02",
|
|
2370
2409
|
"url_field": '<a href="http://example.com/2">http://example.com/2</a>',
|
|
@@ -2372,18 +2411,45 @@ class CustomFieldTableTest(TestCase):
|
|
|
2372
2411
|
"multi_choice_field": (
|
|
2373
2412
|
'<span class="badge bg-secondary">Bar</span> <span class="badge bg-secondary">Baz</span>'
|
|
2374
2413
|
),
|
|
2414
|
+
"json_field": '<pre><code class="language-json">{\n"hello": "world"\n}</code></pre>',
|
|
2415
|
+
"markdown_field": "<h2>Heading</h2>",
|
|
2375
2416
|
}
|
|
2376
2417
|
|
|
2377
2418
|
bound_row = location_table.rows[0]
|
|
2378
2419
|
|
|
2379
2420
|
for col_name, col_expected_value in custom_column_expected.items():
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2421
|
+
with self.subTest(col_name=col_name, col_expected_value=col_expected_value):
|
|
2422
|
+
internal_col_name = "cf_" + col_name
|
|
2423
|
+
custom_column = location_table.base_columns.get(internal_col_name)
|
|
2424
|
+
self.assertIsNotNone(custom_column, internal_col_name)
|
|
2425
|
+
self.assertIsInstance(custom_column, CustomFieldColumn)
|
|
2426
|
+
|
|
2427
|
+
rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
|
|
2428
|
+
self.assertHTMLEqual(str(rendered_value), str(col_expected_value))
|
|
2429
|
+
|
|
2430
|
+
custom_column_expected_2 = {
|
|
2431
|
+
"text_field": "<script></script>",
|
|
2432
|
+
"number_field": 0,
|
|
2433
|
+
"boolean_field": '<span class="text-danger"><i class="mdi mdi-close-thick" title="No"></i></span>',
|
|
2434
|
+
"date_field": '<span class="text-secondary">—</span>',
|
|
2435
|
+
"url_field": '<span class="text-secondary">—</span>',
|
|
2436
|
+
"choice_field": '<span class="text-secondary">—</span>',
|
|
2437
|
+
"multi_choice_field": '<span class="text-secondary">—</span>',
|
|
2438
|
+
"json_field": '<pre><code class="language-json">{}</code></pre>',
|
|
2439
|
+
"markdown_field": '<span class="text-secondary">—</span>',
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
bound_row = location_table.rows[1]
|
|
2443
|
+
|
|
2444
|
+
for col_name, col_expected_value in custom_column_expected_2.items():
|
|
2445
|
+
with self.subTest(col_name=col_name, col_expected_value=col_expected_value):
|
|
2446
|
+
internal_col_name = "cf_" + col_name
|
|
2447
|
+
custom_column = location_table.base_columns.get(internal_col_name)
|
|
2448
|
+
self.assertIsNotNone(custom_column, internal_col_name)
|
|
2449
|
+
self.assertIsInstance(custom_column, CustomFieldColumn)
|
|
2384
2450
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2451
|
+
rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
|
|
2452
|
+
self.assertHTMLEqual(str(rendered_value), str(col_expected_value))
|
|
2387
2453
|
|
|
2388
2454
|
|
|
2389
2455
|
class CustomFieldFilterFormTest(TestCase):
|
|
@@ -2,22 +2,26 @@ from unittest import mock
|
|
|
2
2
|
import uuid
|
|
3
3
|
|
|
4
4
|
from django.core.cache import cache
|
|
5
|
+
from django.test import override_settings
|
|
5
6
|
|
|
6
7
|
from nautobot.core.testing import TestCase
|
|
7
8
|
from nautobot.dcim.models import Cable, Device, PowerPort
|
|
8
9
|
from nautobot.extras.choices import JobQueueTypeChoices
|
|
9
|
-
from nautobot.extras.models import JobQueue
|
|
10
|
+
from nautobot.extras.models import JobQueue, JobResult
|
|
10
11
|
from nautobot.extras.registry import registry
|
|
11
12
|
from nautobot.extras.utils import (
|
|
12
13
|
get_base_template,
|
|
13
14
|
get_celery_queues,
|
|
14
15
|
get_worker_count,
|
|
15
16
|
populate_model_features_registry,
|
|
17
|
+
run_kubernetes_job_and_return_job_result,
|
|
16
18
|
)
|
|
17
19
|
from nautobot.users.models import Token
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class UtilsTestCase(TestCase):
|
|
23
|
+
databases = ("default", "job_logs")
|
|
24
|
+
|
|
21
25
|
def test_get_base_template(self):
|
|
22
26
|
with self.subTest("explicitly specified base_template always wins"):
|
|
23
27
|
self.assertEqual(get_base_template("dcim/device/base.html", Device), "dcim/device/base.html")
|
|
@@ -125,3 +129,114 @@ class UtilsTestCase(TestCase):
|
|
|
125
129
|
original_custom_fields_registry,
|
|
126
130
|
"Registry should be restored to original state",
|
|
127
131
|
)
|
|
132
|
+
|
|
133
|
+
@override_settings(
|
|
134
|
+
KUBERNETES_JOB_POD_NAME="test-pod",
|
|
135
|
+
KUBERNETES_JOB_POD_NAMESPACE="test-namespace",
|
|
136
|
+
KUBERNETES_JOB_MANIFEST={
|
|
137
|
+
"metadata": {"name": "test-job"},
|
|
138
|
+
"spec": {
|
|
139
|
+
"template": {
|
|
140
|
+
"spec": {
|
|
141
|
+
"containers": [
|
|
142
|
+
{
|
|
143
|
+
"command": [],
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
KUBERNETES_SSL_CA_CERT_PATH="/path/to/ca.crt",
|
|
151
|
+
KUBERNETES_TOKEN_PATH="/path/to/token", # noqa: S106
|
|
152
|
+
KUBERNETES_DEFAULT_SERVICE_ADDRESS="https://kubernetes.default.svc",
|
|
153
|
+
)
|
|
154
|
+
@mock.patch("nautobot.extras.utils.transaction.on_commit")
|
|
155
|
+
@mock.patch("builtins.open", new_callable=mock.mock_open, read_data="test-token\n")
|
|
156
|
+
@mock.patch("nautobot.extras.utils.kubernetes.client.BatchV1Api")
|
|
157
|
+
@mock.patch("nautobot.extras.utils.kubernetes.client.ApiClient")
|
|
158
|
+
@mock.patch("nautobot.extras.utils.kubernetes.client.Configuration")
|
|
159
|
+
def test_run_kubernetes_job_and_return_job_result(
|
|
160
|
+
self,
|
|
161
|
+
mock_configuration,
|
|
162
|
+
mock_api_client,
|
|
163
|
+
mock_batch_api,
|
|
164
|
+
mock_open,
|
|
165
|
+
mock_on_commit,
|
|
166
|
+
):
|
|
167
|
+
"""Test run_kubernetes_job_and_return_job_result function."""
|
|
168
|
+
# Setup test data
|
|
169
|
+
job_result = JobResult.objects.create(
|
|
170
|
+
name="Test Job",
|
|
171
|
+
user=self.user,
|
|
172
|
+
)
|
|
173
|
+
# Mock the log method to avoid database writes during test
|
|
174
|
+
job_result.log = mock.Mock()
|
|
175
|
+
job_kwargs = '{"key": "value"}'
|
|
176
|
+
|
|
177
|
+
# Setup kubernetes client mocks
|
|
178
|
+
mock_config_instance = mock.MagicMock()
|
|
179
|
+
mock_config_instance.api_key_prefix = {}
|
|
180
|
+
mock_config_instance.api_key = {}
|
|
181
|
+
mock_configuration.return_value = mock_config_instance
|
|
182
|
+
|
|
183
|
+
mock_api_client_instance = mock.Mock()
|
|
184
|
+
mock_api_client.return_value.__enter__.return_value = mock_api_client_instance
|
|
185
|
+
mock_api_client.return_value.__exit__.return_value = None
|
|
186
|
+
|
|
187
|
+
mock_api_instance = mock.Mock()
|
|
188
|
+
mock_batch_api.return_value = mock_api_instance
|
|
189
|
+
|
|
190
|
+
# Capture the callback passed to transaction.on_commit
|
|
191
|
+
commit_callback = None
|
|
192
|
+
|
|
193
|
+
def capture_callback(callback):
|
|
194
|
+
nonlocal commit_callback
|
|
195
|
+
commit_callback = callback
|
|
196
|
+
# Execute immediately for testing
|
|
197
|
+
callback()
|
|
198
|
+
|
|
199
|
+
mock_on_commit.side_effect = capture_callback
|
|
200
|
+
|
|
201
|
+
# Execute the function
|
|
202
|
+
result = run_kubernetes_job_and_return_job_result(job_result, job_kwargs)
|
|
203
|
+
|
|
204
|
+
# Verify job_result was updated and saved
|
|
205
|
+
job_result.refresh_from_db()
|
|
206
|
+
self.assertEqual(job_result.task_kwargs, job_kwargs)
|
|
207
|
+
self.assertEqual(result, job_result)
|
|
208
|
+
|
|
209
|
+
# Verify transaction.on_commit was called
|
|
210
|
+
mock_on_commit.assert_called_once()
|
|
211
|
+
self.assertIsNotNone(commit_callback)
|
|
212
|
+
|
|
213
|
+
# Verify kubernetes configuration was set up correctly
|
|
214
|
+
mock_configuration.assert_called_once()
|
|
215
|
+
self.assertEqual(mock_config_instance.host, "https://kubernetes.default.svc")
|
|
216
|
+
self.assertEqual(mock_config_instance.ssl_ca_cert, "/path/to/ca.crt")
|
|
217
|
+
self.assertEqual(mock_config_instance.api_key_prefix["authorization"], "Bearer")
|
|
218
|
+
self.assertEqual(mock_config_instance.api_key["authorization"], "test-token")
|
|
219
|
+
|
|
220
|
+
# Verify ApiClient was used as context manager
|
|
221
|
+
mock_api_client.assert_called_once_with(mock_config_instance)
|
|
222
|
+
|
|
223
|
+
# Verify BatchV1Api was created with the api_client_instance
|
|
224
|
+
mock_batch_api.assert_called_once_with(mock_api_client_instance)
|
|
225
|
+
|
|
226
|
+
# Verify the pod manifest was modified correctly
|
|
227
|
+
mock_api_instance.create_namespaced_job.assert_called_once()
|
|
228
|
+
create_call = mock_api_instance.create_namespaced_job.call_args
|
|
229
|
+
body = create_call[1]["body"]
|
|
230
|
+
self.assertEqual(body["metadata"]["name"], f"nautobot-job-{job_result.pk}")
|
|
231
|
+
self.assertEqual(
|
|
232
|
+
body["spec"]["template"]["spec"]["containers"][0]["command"],
|
|
233
|
+
["nautobot-server", "runjob_with_job_result", str(job_result.pk)],
|
|
234
|
+
)
|
|
235
|
+
self.assertEqual(create_call[1]["namespace"], "test-namespace")
|
|
236
|
+
|
|
237
|
+
# Verify token file was opened
|
|
238
|
+
mock_open.assert_called_once_with("/path/to/token", "r", encoding="utf-8")
|
|
239
|
+
|
|
240
|
+
# Verify job_result.log was called (checking for log messages)
|
|
241
|
+
self.assertEqual(job_result.log.call_count, 1)
|
|
242
|
+
self.assertIn("Creating job pod", str(job_result.log.call_args_list[0]))
|
nautobot/extras/utils.py
CHANGED
|
@@ -668,7 +668,7 @@ def refresh_job_model_from_job_class(job_model_class, job_class, job_queue_class
|
|
|
668
668
|
return (job_model, created)
|
|
669
669
|
|
|
670
670
|
|
|
671
|
-
def run_kubernetes_job_and_return_job_result(
|
|
671
|
+
def run_kubernetes_job_and_return_job_result(job_result, job_kwargs):
|
|
672
672
|
"""
|
|
673
673
|
Pass the job to a kubernetes pod and execute it there.
|
|
674
674
|
"""
|
|
@@ -678,17 +678,6 @@ def run_kubernetes_job_and_return_job_result(job_queue, job_result, job_kwargs):
|
|
|
678
678
|
pod_ssl_ca_cert = settings.KUBERNETES_SSL_CA_CERT_PATH
|
|
679
679
|
pod_token = settings.KUBERNETES_TOKEN_PATH
|
|
680
680
|
|
|
681
|
-
configuration = kubernetes.client.Configuration()
|
|
682
|
-
configuration.host = settings.KUBERNETES_DEFAULT_SERVICE_ADDRESS
|
|
683
|
-
configuration.ssl_ca_cert = pod_ssl_ca_cert
|
|
684
|
-
with open(pod_token, "r") as token_file:
|
|
685
|
-
token = token_file.read().strip()
|
|
686
|
-
# configure API Key authorization: BearerToken
|
|
687
|
-
configuration.api_key_prefix["authorization"] = "Bearer"
|
|
688
|
-
configuration.api_key["authorization"] = token
|
|
689
|
-
with kubernetes.client.ApiClient(configuration) as api_client:
|
|
690
|
-
api_instance = kubernetes.client.BatchV1Api(api_client)
|
|
691
|
-
|
|
692
681
|
job_result.task_kwargs = job_kwargs
|
|
693
682
|
job_result.save()
|
|
694
683
|
pod_manifest["metadata"]["name"] = "nautobot-job-" + str(job_result.pk)
|
|
@@ -697,10 +686,23 @@ def run_kubernetes_job_and_return_job_result(job_queue, job_result, job_kwargs):
|
|
|
697
686
|
"runjob_with_job_result",
|
|
698
687
|
f"{job_result.pk}",
|
|
699
688
|
]
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
689
|
+
|
|
690
|
+
def create_kubernetes_job():
|
|
691
|
+
"""Create and read the Kubernetes job after the transaction commits."""
|
|
692
|
+
configuration = kubernetes.client.Configuration()
|
|
693
|
+
configuration.host = settings.KUBERNETES_DEFAULT_SERVICE_ADDRESS
|
|
694
|
+
configuration.ssl_ca_cert = pod_ssl_ca_cert
|
|
695
|
+
with open(pod_token, "r", encoding="utf-8") as token_file:
|
|
696
|
+
token = token_file.read().strip()
|
|
697
|
+
# configure API Key authorization: BearerToken
|
|
698
|
+
configuration.api_key_prefix["authorization"] = "Bearer"
|
|
699
|
+
configuration.api_key["authorization"] = token
|
|
700
|
+
with kubernetes.client.ApiClient(configuration) as api_client:
|
|
701
|
+
api_instance = kubernetes.client.BatchV1Api(api_client)
|
|
702
|
+
job_result.log(f"Creating job pod {pod_name} in namespace {pod_namespace}")
|
|
703
|
+
api_instance.create_namespaced_job(body=pod_manifest, namespace=pod_namespace)
|
|
704
|
+
|
|
705
|
+
transaction.on_commit(create_kubernetes_job)
|
|
704
706
|
return job_result
|
|
705
707
|
|
|
706
708
|
|
nautobot/extras/views.py
CHANGED
|
@@ -820,7 +820,6 @@ class ComputedFieldUIViewSet(NautobotUIViewSet):
|
|
|
820
820
|
serializer_class = serializers.ComputedFieldSerializer
|
|
821
821
|
table_class = tables.ComputedFieldTable
|
|
822
822
|
queryset = ComputedField.objects.all()
|
|
823
|
-
action_buttons = ("add",)
|
|
824
823
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
825
824
|
panels=(
|
|
826
825
|
object_detail.ObjectFieldsPanel(
|
|
@@ -1246,7 +1245,6 @@ class CustomFieldUIViewSet(NautobotUIViewSet):
|
|
|
1246
1245
|
form_class = forms.CustomFieldForm
|
|
1247
1246
|
table_class = tables.CustomFieldTable
|
|
1248
1247
|
template_name = "extras/customfield_update.html"
|
|
1249
|
-
action_buttons = ("add",)
|
|
1250
1248
|
|
|
1251
1249
|
class CustomFieldObjectFieldsPanel(object_detail.ObjectFieldsPanel):
|
|
1252
1250
|
def render_value(self, key, value, context):
|
|
@@ -1392,7 +1390,6 @@ class DynamicGroupUIViewSet(NautobotUIViewSet):
|
|
|
1392
1390
|
queryset = DynamicGroup.objects.all()
|
|
1393
1391
|
serializer_class = serializers.DynamicGroupSerializer
|
|
1394
1392
|
table_class = tables.DynamicGroupTable
|
|
1395
|
-
action_buttons = ("add",)
|
|
1396
1393
|
|
|
1397
1394
|
def get_extra_context(self, request, instance):
|
|
1398
1395
|
context = super().get_extra_context(request, instance)
|
|
@@ -1896,23 +1893,14 @@ class GitRepositoryUIViewSet(NautobotUIViewSet):
|
|
|
1896
1893
|
#
|
|
1897
1894
|
|
|
1898
1895
|
|
|
1899
|
-
class GraphQLQueryUIViewSet(
|
|
1900
|
-
ObjectDetailViewMixin,
|
|
1901
|
-
ObjectListViewMixin,
|
|
1902
|
-
ObjectEditViewMixin,
|
|
1903
|
-
ObjectDestroyViewMixin,
|
|
1904
|
-
ObjectBulkDestroyViewMixin,
|
|
1905
|
-
ObjectChangeLogViewMixin,
|
|
1906
|
-
ObjectDataComplianceViewMixin,
|
|
1907
|
-
ObjectNotesViewMixin,
|
|
1908
|
-
):
|
|
1896
|
+
class GraphQLQueryUIViewSet(NautobotUIViewSet):
|
|
1909
1897
|
filterset_form_class = forms.GraphQLQueryFilterForm
|
|
1910
1898
|
queryset = GraphQLQuery.objects.all()
|
|
1911
1899
|
form_class = forms.GraphQLQueryForm
|
|
1912
1900
|
filterset_class = filters.GraphQLQueryFilterSet
|
|
1913
1901
|
serializer_class = serializers.GraphQLQuerySerializer
|
|
1914
1902
|
table_class = tables.GraphQLQueryTable
|
|
1915
|
-
action_buttons = ("add",)
|
|
1903
|
+
action_buttons = ("add", "export", "import")
|
|
1916
1904
|
|
|
1917
1905
|
object_detail_content = object_detail.ObjectDetailContent(
|
|
1918
1906
|
panels=(
|
nautobot/ipam/apps.py
CHANGED
nautobot/ipam/filters.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import ipaddress
|
|
2
3
|
import uuid
|
|
3
4
|
|
|
4
5
|
from django.core.exceptions import ValidationError
|
|
@@ -221,6 +222,10 @@ class PrefixFilterSet(
|
|
|
221
222
|
method="filter_prefix",
|
|
222
223
|
label="Prefix",
|
|
223
224
|
)
|
|
225
|
+
prefix_exact = MultiValueCharFilter(
|
|
226
|
+
method="filter_prefix_exact",
|
|
227
|
+
label="Prefix (exact, strict)",
|
|
228
|
+
)
|
|
224
229
|
within = MultiValueCharFilter(
|
|
225
230
|
method="search_within",
|
|
226
231
|
label="Within prefix",
|
|
@@ -327,6 +332,20 @@ class PrefixFilterSet(
|
|
|
327
332
|
return queryset.net_equals(*prefixes)
|
|
328
333
|
return queryset.none()
|
|
329
334
|
|
|
335
|
+
def filter_prefix_exact(self, queryset, name, value):
|
|
336
|
+
"""
|
|
337
|
+
Strict version of `prefix` filter.
|
|
338
|
+
Rejects prefixes with host bits set (e.g. 10.32.0.34/28 vs 10.32.0.32/28).
|
|
339
|
+
"""
|
|
340
|
+
prefixes = self._strip_values(value)
|
|
341
|
+
|
|
342
|
+
for prefix in prefixes:
|
|
343
|
+
try:
|
|
344
|
+
ipaddress.ip_network(prefix, strict=True)
|
|
345
|
+
except ValueError:
|
|
346
|
+
raise ValidationError(f"Invalid prefix_exact value as it is not a subnet boundary: {prefix}.")
|
|
347
|
+
return self.filter_prefix(queryset, name, value)
|
|
348
|
+
|
|
330
349
|
def search_within(self, queryset, name, value):
|
|
331
350
|
prefixes = self._strip_values(value)
|
|
332
351
|
with contextlib.suppress(netaddr.AddrFormatError, ValueError):
|
|
@@ -417,6 +436,10 @@ class IPAddressFilterSet(
|
|
|
417
436
|
method="search_by_prefix",
|
|
418
437
|
label="Contained in prefix",
|
|
419
438
|
)
|
|
439
|
+
prefix_exact = MultiValueCharFilter(
|
|
440
|
+
method="search_by_prefix_exact",
|
|
441
|
+
label="Prefix (exact, strict)",
|
|
442
|
+
)
|
|
420
443
|
address = MultiValueCharFilter(
|
|
421
444
|
method="filter_address",
|
|
422
445
|
label="Address",
|
|
@@ -512,17 +535,49 @@ class IPAddressFilterSet(
|
|
|
512
535
|
params = self.generate_query__has_interface_assignments(value)
|
|
513
536
|
return queryset.filter(params)
|
|
514
537
|
|
|
515
|
-
def
|
|
538
|
+
def _strip_prefix_values(self, values):
|
|
539
|
+
"""Normalize inputs: strip whitespace + resolve UUIDs to Prefix.prefix."""
|
|
516
540
|
prefixes = []
|
|
517
|
-
for prefix in
|
|
541
|
+
for prefix in values:
|
|
518
542
|
prefix = prefix.strip()
|
|
543
|
+
if not prefix:
|
|
544
|
+
continue
|
|
519
545
|
if is_uuid(prefix):
|
|
520
546
|
prefixes.append(Prefix.objects.get(pk=prefix).prefix)
|
|
521
|
-
|
|
547
|
+
else:
|
|
522
548
|
prefixes.append(prefix)
|
|
549
|
+
return prefixes
|
|
523
550
|
|
|
551
|
+
def search_by_prefix(self, queryset, name, value):
|
|
552
|
+
prefixes = self._strip_prefix_values(value)
|
|
524
553
|
return queryset.net_host_contained(*prefixes)
|
|
525
554
|
|
|
555
|
+
def search_by_prefix_exact(self, queryset, name, value):
|
|
556
|
+
"""
|
|
557
|
+
Strict version of `prefix` filter.
|
|
558
|
+
Rejects prefixes with host bits set (e.g. 10.32.0.34/28 vs 10.32.0.32/28).
|
|
559
|
+
"""
|
|
560
|
+
prefixes = self._strip_prefix_values(value)
|
|
561
|
+
|
|
562
|
+
# Validate network is on CIDR boundary
|
|
563
|
+
for prefix in prefixes:
|
|
564
|
+
if "/" not in str(prefix):
|
|
565
|
+
# If someone passes a host-only string here, treat it as invalid for "prefix_exact".
|
|
566
|
+
raise ValidationError(f"Invalid prefix_exact value (missing mask): {prefix}")
|
|
567
|
+
|
|
568
|
+
with contextlib.suppress(netaddr.AddrFormatError, ValueError):
|
|
569
|
+
ip_network = netaddr.IPNetwork(str(prefix)).cidr
|
|
570
|
+
# cidr will always a proper network subnet; compare against original input
|
|
571
|
+
if str(ip_network) != str(prefix):
|
|
572
|
+
raise ValidationError(
|
|
573
|
+
f"Invalid prefix_exact value as it is not a subnet boundary: {prefix}, did you mean {ip_network}?"
|
|
574
|
+
)
|
|
575
|
+
continue
|
|
576
|
+
|
|
577
|
+
# Defensive programming in case there is logic missed above
|
|
578
|
+
raise ValidationError(f"Invalid prefix_exact value as it is not a subnet boundary: {prefix}.")
|
|
579
|
+
return self.search_by_prefix(queryset, name, prefixes)
|
|
580
|
+
|
|
526
581
|
def filter_address(self, queryset, name, value):
|
|
527
582
|
try:
|
|
528
583
|
return queryset.net_in(value)
|
nautobot/ipam/tables.py
CHANGED
|
@@ -40,7 +40,11 @@ AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
|
|
|
40
40
|
|
|
41
41
|
UTILIZATION_GRAPH = """
|
|
42
42
|
{% load helpers %}
|
|
43
|
-
{% if record.present_in_database %}
|
|
43
|
+
{% if record.present_in_database %}
|
|
44
|
+
{% utilization_graph record.get_utilization %}
|
|
45
|
+
{% else %}
|
|
46
|
+
<span class="text-secondary">—</span>
|
|
47
|
+
{% endif %}
|
|
44
48
|
"""
|
|
45
49
|
|
|
46
50
|
|
|
@@ -73,7 +77,7 @@ PREFIX_ROLE_LINK = """
|
|
|
73
77
|
{% if record.role %}
|
|
74
78
|
<a href="{% url 'ipam:prefix_list' %}?role={{ record.role.name }}">{{ record.role }}</a>
|
|
75
79
|
{% else %}
|
|
76
|
-
|
|
80
|
+
<span class="text-secondary">—</span>
|
|
77
81
|
{% endif %}
|
|
78
82
|
"""
|
|
79
83
|
|
|
@@ -157,7 +161,7 @@ VRF_TARGETS = """
|
|
|
157
161
|
{% for rt in value.all %}
|
|
158
162
|
<a href="{{ rt.get_absolute_url }}">{{ rt }}</a>{% if not forloop.last %}<br />{% endif %}
|
|
159
163
|
{% empty %}
|
|
160
|
-
|
|
164
|
+
<span class="text-secondary">—</span>
|
|
161
165
|
{% endfor %}
|
|
162
166
|
"""
|
|
163
167
|
|
|
@@ -179,7 +183,7 @@ VLAN_PREFIXES = """
|
|
|
179
183
|
{% for prefix in record.prefixes.all %}
|
|
180
184
|
<a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
|
|
181
185
|
{% empty %}
|
|
182
|
-
|
|
186
|
+
<span class="text-secondary">—</span>
|
|
183
187
|
{% endfor %}
|
|
184
188
|
"""
|
|
185
189
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
+
from django.core.exceptions import ValidationError
|
|
2
3
|
from django.db.models import Q
|
|
3
4
|
|
|
4
5
|
from nautobot.core.testing import FilterTestCases, TestCase
|
|
@@ -242,6 +243,32 @@ class PrefixTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyFilt
|
|
|
242
243
|
ancestors = [ancestor.id for ancestor in prefixes[2].ancestors()]
|
|
243
244
|
self.assertQuerysetEqualAndNotEmpty(filterset.qs, self.queryset.filter(id__in=ancestors))
|
|
244
245
|
|
|
246
|
+
def test_prefix(self):
|
|
247
|
+
Prefix.objects.create(
|
|
248
|
+
prefix="192.0.2.0/29",
|
|
249
|
+
type=PrefixTypeChoices.TYPE_POOL,
|
|
250
|
+
namespace=Namespace.objects.first(),
|
|
251
|
+
status=Status.objects.get_for_model(Prefix).first(),
|
|
252
|
+
)
|
|
253
|
+
params = {"prefix": "192.0.2.0/29"}
|
|
254
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
255
|
+
params = {"prefix": "192.0.2.1/29"}
|
|
256
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
257
|
+
|
|
258
|
+
def test_prefix_exact(self):
|
|
259
|
+
Prefix.objects.create(
|
|
260
|
+
prefix="192.0.2.0/29",
|
|
261
|
+
type=PrefixTypeChoices.TYPE_POOL,
|
|
262
|
+
namespace=Namespace.objects.first(),
|
|
263
|
+
status=Status.objects.get_for_model(Prefix).first(),
|
|
264
|
+
)
|
|
265
|
+
params = {"prefix_exact": "192.0.2.0/29"}
|
|
266
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
267
|
+
params = {"prefix_exact": "192.0.2.1/29"}
|
|
268
|
+
with self.assertRaises(ValidationError) as exc:
|
|
269
|
+
self.filterset(params, self.queryset).qs # pylint: disable=expression-not-assigned
|
|
270
|
+
self.assertTrue("Invalid prefix_exact value" in str(exc.exception))
|
|
271
|
+
|
|
245
272
|
|
|
246
273
|
class PrefixLocationAssignmentTestCase(FilterTestCases.FilterTestCase):
|
|
247
274
|
queryset = PrefixLocationAssignment.objects.all()
|
|
@@ -807,6 +834,34 @@ class IPAddressTestCase(FilterTestCases.FilterTestCase, FilterTestCases.TenancyF
|
|
|
807
834
|
self.filterset(params, self.queryset).qs, self.queryset.net_host_contained(ipv4_parent, ipv6_parent)
|
|
808
835
|
)
|
|
809
836
|
|
|
837
|
+
def test_prefix_exact(self):
|
|
838
|
+
ipv4_parent = self.queryset.filter(ip_version=4).first().address.supernet()[-1]
|
|
839
|
+
ipv6_parent = self.queryset.filter(ip_version=6).first().address.supernet()[-1]
|
|
840
|
+
|
|
841
|
+
params = {"prefix_exact": [str(ipv4_parent), str(ipv6_parent)]}
|
|
842
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
843
|
+
self.filterset(params, self.queryset).qs, self.queryset.net_host_contained(ipv4_parent, ipv6_parent)
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# Get the first usable IP address in the subnet (not the network address)
|
|
847
|
+
ipv4_parent = self.queryset.filter(ip_version=4).first().address
|
|
848
|
+
ipv6_parent = self.queryset.filter(ip_version=6).first().address
|
|
849
|
+
|
|
850
|
+
params = {"prefix_exact": [str(ipv4_parent)]}
|
|
851
|
+
with self.assertRaises(ValidationError) as exc:
|
|
852
|
+
self.filterset(params, self.queryset).qs # pylint: disable=expression-not-assigned
|
|
853
|
+
self.assertTrue("Invalid prefix_exact value" in str(exc.exception))
|
|
854
|
+
|
|
855
|
+
params = {"prefix_exact": [str(ipv6_parent)]}
|
|
856
|
+
with self.assertRaises(ValidationError) as exc:
|
|
857
|
+
self.filterset(params, self.queryset).qs # pylint: disable=expression-not-assigned
|
|
858
|
+
self.assertTrue("Invalid prefix_exact value" in str(exc.exception))
|
|
859
|
+
|
|
860
|
+
params = {"prefix_exact": ["10.1.1.1"]}
|
|
861
|
+
with self.assertRaises(ValidationError) as exc:
|
|
862
|
+
self.filterset(params, self.queryset).qs # pylint: disable=expression-not-assigned
|
|
863
|
+
self.assertTrue("Invalid prefix_exact value (missing mask)" in str(exc.exception))
|
|
864
|
+
|
|
810
865
|
def test_filter_address(self):
|
|
811
866
|
"""Check IPv4 and IPv6, with and without a mask"""
|
|
812
867
|
ipv4_addresses = self.queryset.filter(ip_version=4)[:2]
|