nautobot 2.3.7__py3-none-any.whl → 2.3.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/tables.py +2 -0
- nautobot/core/forms/__init__.py +4 -0
- nautobot/core/forms/fields.py +32 -0
- nautobot/core/jobs/__init__.py +24 -8
- nautobot/core/models/tree_queries.py +8 -0
- nautobot/core/settings.py +7 -0
- nautobot/core/settings.yaml +10 -0
- nautobot/core/signals.py +5 -4
- nautobot/core/templates/inc/paginator.html +3 -0
- nautobot/core/templates/nautobot_config.py.j2 +4 -0
- nautobot/core/templates/utilities/obj_table.html +1 -1
- nautobot/dcim/api/serializers.py +10 -5
- nautobot/dcim/forms.py +41 -34
- nautobot/dcim/models/device_components.py +12 -4
- nautobot/dcim/tables/devices.py +4 -2
- nautobot/dcim/tests/test_api.py +28 -0
- nautobot/dcim/tests/test_forms.py +17 -1
- nautobot/dcim/tests/test_models.py +58 -4
- nautobot/dcim/utils.py +9 -6
- nautobot/extras/context_managers.py +7 -8
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +30 -49
- nautobot/extras/datasources/registry.py +2 -2
- nautobot/extras/jobs.py +17 -5
- nautobot/extras/models/datasources.py +6 -0
- nautobot/extras/models/groups.py +47 -33
- nautobot/extras/models/jobs.py +1 -1
- nautobot/extras/plugins/__init__.py +165 -0
- nautobot/extras/plugins/views.py +18 -3
- nautobot/extras/templates/extras/plugin_detail.html +33 -0
- nautobot/extras/tests/test_context_managers.py +16 -7
- nautobot/extras/tests/test_datasources.py +88 -1
- nautobot/extras/tests/test_dynamicgroups.py +12 -0
- nautobot/extras/tests/test_plugins.py +94 -0
- nautobot/extras/views.py +3 -1
- nautobot/ipam/filters.py +2 -2
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/templates/ipam/ipaddress.html +2 -2
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +3 -0
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +3 -0
- nautobot/ipam/templates/ipam/prefix.html +3 -3
- nautobot/ipam/templates/ipam/routetarget.html +2 -2
- nautobot/ipam/templates/ipam/vlan.html +3 -0
- nautobot/ipam/templates/ipam/vrf.html +7 -4
- nautobot/ipam/tests/test_models.py +68 -12
- nautobot/ipam/views.py +43 -0
- nautobot/project-static/docs/404.html +24 -3
- nautobot/project-static/docs/apps/index.html +24 -3
- nautobot/project-static/docs/apps/nautobot-apps.html +24 -3
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js → bundle.83f73b43.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.525ec568.min.js.map → bundle.83f73b43.min.js.map} +3 -3
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.0253249f.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +49 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +138 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +24 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +24 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +24 -3
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/global-search.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +24 -3
- nautobot/project-static/docs/development/apps/api/models/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +24 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +27 -6
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +8823 -0
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +27 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +24 -3
- nautobot/project-static/docs/development/apps/api/setup.html +33 -11
- nautobot/project-static/docs/development/apps/api/testing.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +24 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/index.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/notes.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +24 -3
- nautobot/project-static/docs/development/apps/api/views/urls.html +24 -3
- nautobot/project-static/docs/development/apps/index.html +24 -3
- nautobot/project-static/docs/development/apps/migration/code-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +24 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +24 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +24 -3
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +24 -3
- nautobot/project-static/docs/development/core/application-registry.html +24 -3
- nautobot/project-static/docs/development/core/best-practices.html +24 -3
- nautobot/project-static/docs/development/core/bootstrap-ui.html +24 -3
- nautobot/project-static/docs/development/core/caching.html +24 -3
- nautobot/project-static/docs/development/core/controllers.html +24 -3
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +24 -3
- nautobot/project-static/docs/development/core/generic-views.html +24 -3
- nautobot/project-static/docs/development/core/getting-started.html +24 -3
- nautobot/project-static/docs/development/core/homepage.html +24 -3
- nautobot/project-static/docs/development/core/index.html +24 -3
- nautobot/project-static/docs/development/core/model-checklist.html +24 -3
- nautobot/project-static/docs/development/core/model-features.html +24 -3
- nautobot/project-static/docs/development/core/natural-keys.html +24 -3
- nautobot/project-static/docs/development/core/navigation-menu.html +24 -3
- nautobot/project-static/docs/development/core/release-checklist.html +24 -3
- nautobot/project-static/docs/development/core/role-internals.html +24 -3
- nautobot/project-static/docs/development/core/settings.html +24 -3
- nautobot/project-static/docs/development/core/style-guide.html +24 -3
- nautobot/project-static/docs/development/core/templates.html +24 -3
- nautobot/project-static/docs/development/core/testing.html +24 -3
- nautobot/project-static/docs/development/core/user-preferences.html +24 -3
- nautobot/project-static/docs/development/index.html +24 -3
- nautobot/project-static/docs/development/jobs/index.html +24 -3
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +24 -3
- nautobot/project-static/docs/index.html +24 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +24 -3
- nautobot/project-static/docs/overview/design_philosophy.html +24 -3
- nautobot/project-static/docs/release-notes/index.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.3.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.4.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +24 -3
- nautobot/project-static/docs/release-notes/version-1.6.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.0.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.1.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.2.html +24 -3
- nautobot/project-static/docs/release-notes/version-2.3.html +337 -109
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +273 -269
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +24 -3
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +29 -4
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +24 -3
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +24 -3
- nautobot/project-static/docs/user-guide/administration/installation/services.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +24 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +24 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +24 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +24 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +24 -3
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +24 -3
- nautobot/project-static/docs/user-guide/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +24 -3
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +24 -3
- nautobot/project-static/js/forms.js +41 -5
- nautobot/virtualization/tables.py +1 -1
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/METADATA +2 -2
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/RECORD +334 -333
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +0 -1
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/NOTICE +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/WHEEL +0 -0
- {nautobot-2.3.7.dist-info → nautobot-2.3.9.dist-info}/entry_points.txt +0 -0
nautobot/extras/plugins/views.py
CHANGED
|
@@ -30,6 +30,21 @@ class InstalledAppsView(GenericView):
|
|
|
30
30
|
data = []
|
|
31
31
|
for app in apps.get_app_configs():
|
|
32
32
|
if app.name in settings.PLUGINS:
|
|
33
|
+
try:
|
|
34
|
+
reverse(app.home_view_name)
|
|
35
|
+
home_url = app.home_view_name
|
|
36
|
+
except NoReverseMatch:
|
|
37
|
+
home_url = None
|
|
38
|
+
try:
|
|
39
|
+
reverse(app.config_view_name)
|
|
40
|
+
config_url = app.config_view_name
|
|
41
|
+
except NoReverseMatch:
|
|
42
|
+
config_url = None
|
|
43
|
+
try:
|
|
44
|
+
reverse(app.docs_view_name)
|
|
45
|
+
docs_url = app.docs_view_name
|
|
46
|
+
except NoReverseMatch:
|
|
47
|
+
docs_url = None
|
|
33
48
|
data.append(
|
|
34
49
|
{
|
|
35
50
|
"name": app.verbose_name,
|
|
@@ -40,9 +55,9 @@ class InstalledAppsView(GenericView):
|
|
|
40
55
|
"description": app.description,
|
|
41
56
|
"version": app.version,
|
|
42
57
|
"actions": {
|
|
43
|
-
"home":
|
|
44
|
-
"configure":
|
|
45
|
-
"docs":
|
|
58
|
+
"home": home_url,
|
|
59
|
+
"configure": config_url,
|
|
60
|
+
"docs": docs_url,
|
|
46
61
|
},
|
|
47
62
|
}
|
|
48
63
|
)
|
|
@@ -277,6 +277,39 @@
|
|
|
277
277
|
{% endif %}
|
|
278
278
|
</td>
|
|
279
279
|
</tr>
|
|
280
|
+
<tr>
|
|
281
|
+
<td>Table Extensions</td>
|
|
282
|
+
<td>
|
|
283
|
+
{% if features.table_extensions %}
|
|
284
|
+
{% if features.table_extensions.columns %}
|
|
285
|
+
<b>Custom Columns</b>
|
|
286
|
+
<ul class="list-unstyled">
|
|
287
|
+
{% for column in features.table_extensions.columns %}
|
|
288
|
+
<li><code>{{ column }}</code></li>
|
|
289
|
+
{% endfor %}
|
|
290
|
+
</ul>
|
|
291
|
+
{% endif %}
|
|
292
|
+
{% if features.table_extensions.add_to_default_columns %}
|
|
293
|
+
<b>Additional Default Columns</b>
|
|
294
|
+
<ul class="list-unstyled">
|
|
295
|
+
{% for column in features.table_extensions.add_to_default_columns %}
|
|
296
|
+
<li><code>{{ column }}</code></li>
|
|
297
|
+
{% endfor %}
|
|
298
|
+
</ul>
|
|
299
|
+
{% endif %}
|
|
300
|
+
{% if features.table_extensions.remove_from_default_columns %}
|
|
301
|
+
<b>Remove from Default Columns</b>
|
|
302
|
+
<ul class="list-unstyled">
|
|
303
|
+
{% for column in features.table_extensions.remove_from_default_columns %}
|
|
304
|
+
<li><code>{{ column }}</code></li>
|
|
305
|
+
{% endfor %}
|
|
306
|
+
</ul>
|
|
307
|
+
{% endif %}
|
|
308
|
+
{% else %}
|
|
309
|
+
{% include 'utilities/render_boolean.html' with value=features.table_extensions %}
|
|
310
|
+
{% endif %}
|
|
311
|
+
</td>
|
|
312
|
+
</tr>
|
|
280
313
|
<tr>
|
|
281
314
|
<td>Views/URLs</td>
|
|
282
315
|
<td>
|
|
@@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
5
5
|
from django.test import TestCase
|
|
6
6
|
|
|
7
7
|
from nautobot.core.celery import app
|
|
8
|
-
from nautobot.core.testing import TransactionTestCase
|
|
8
|
+
from nautobot.core.testing import get_job_class_and_model, TransactionTestCase
|
|
9
9
|
from nautobot.core.utils.lookup import get_changes_for_model
|
|
10
10
|
from nautobot.dcim.models import (
|
|
11
11
|
DeviceType,
|
|
@@ -22,7 +22,7 @@ from nautobot.extras.context_managers import (
|
|
|
22
22
|
deferred_change_logging_for_bulk_operation,
|
|
23
23
|
web_request_context,
|
|
24
24
|
)
|
|
25
|
-
from nautobot.extras.models import Status, Webhook
|
|
25
|
+
from nautobot.extras.models import JobHook, Status, Webhook
|
|
26
26
|
from nautobot.extras.utils import bulk_delete_with_bulk_change_logging
|
|
27
27
|
|
|
28
28
|
# Use the proper swappable User model
|
|
@@ -74,7 +74,7 @@ class WebRequestContextTestCase(TestCase):
|
|
|
74
74
|
self.assertEqual(oc_list[0].changed_object, location)
|
|
75
75
|
self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_CREATE)
|
|
76
76
|
|
|
77
|
-
@mock.patch("nautobot.extras.jobs.enqueue_job_hooks")
|
|
77
|
+
@mock.patch("nautobot.extras.jobs.enqueue_job_hooks", return_value=True)
|
|
78
78
|
@mock.patch("nautobot.extras.context_managers.enqueue_webhooks")
|
|
79
79
|
def test_create_then_delete(self, mock_enqueue_webhooks, mock_enqueue_job_hooks):
|
|
80
80
|
"""Test that a create followed by a delete is logged as two changes"""
|
|
@@ -88,11 +88,13 @@ class WebRequestContextTestCase(TestCase):
|
|
|
88
88
|
|
|
89
89
|
location = Location.objects.filter(pk=location_pk)
|
|
90
90
|
self.assertFalse(location.exists())
|
|
91
|
-
oc_list = get_changes_for_model(Location).filter(changed_object_id=location_pk)
|
|
91
|
+
oc_list = get_changes_for_model(Location).filter(changed_object_id=location_pk).order_by("time")
|
|
92
92
|
self.assertEqual(len(oc_list), 2)
|
|
93
|
-
self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.
|
|
94
|
-
self.assertEqual(oc_list[1].action, ObjectChangeActionChoices.
|
|
95
|
-
mock_enqueue_job_hooks.assert_has_calls(
|
|
93
|
+
self.assertEqual(oc_list[0].action, ObjectChangeActionChoices.ACTION_CREATE)
|
|
94
|
+
self.assertEqual(oc_list[1].action, ObjectChangeActionChoices.ACTION_DELETE)
|
|
95
|
+
mock_enqueue_job_hooks.assert_has_calls(
|
|
96
|
+
[mock.call(oc_list[0], may_reload_jobs=True), mock.call(oc_list[1], may_reload_jobs=False)],
|
|
97
|
+
)
|
|
96
98
|
mock_enqueue_webhooks.assert_has_calls([mock.call(oc_list[0]), mock.call(oc_list[1])])
|
|
97
99
|
|
|
98
100
|
def test_update_then_delete(self):
|
|
@@ -307,6 +309,13 @@ class BulkEditDeleteChangeLogging(TestCase):
|
|
|
307
309
|
for i in range(1, 4)
|
|
308
310
|
]
|
|
309
311
|
Location.objects.bulk_create(locations)
|
|
312
|
+
# Create a JobHook that applies to Locations
|
|
313
|
+
_, job_model = get_job_class_and_model("job_hook_receiver", "TestJobHookReceiverLog")
|
|
314
|
+
mock_import_jobs.assert_called_once()
|
|
315
|
+
mock_import_jobs.reset_mock()
|
|
316
|
+
job_hook = JobHook.objects.create(name="JobHookTest", type_update=True, job=job_model)
|
|
317
|
+
job_hook.content_types.set([ContentType.objects.get_for_model(Location)])
|
|
318
|
+
|
|
310
319
|
pk_list = []
|
|
311
320
|
with web_request_context(self.user):
|
|
312
321
|
with deferred_change_logging_for_bulk_operation():
|
|
@@ -412,7 +412,7 @@ class GitTest(TransactionTestCase):
|
|
|
412
412
|
|
|
413
413
|
def test_pull_git_repository_and_refresh_data_with_bad_data(self):
|
|
414
414
|
"""
|
|
415
|
-
The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository
|
|
415
|
+
The test_pull_git_repository_and_refresh_data job should gracefully handle bad data in the Git repository.
|
|
416
416
|
"""
|
|
417
417
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
418
418
|
with self.settings(GIT_ROOT=tempdir):
|
|
@@ -433,6 +433,18 @@ class GitTest(TransactionTestCase):
|
|
|
433
433
|
job_result.result,
|
|
434
434
|
)
|
|
435
435
|
|
|
436
|
+
# Due to transaction rollback on failure, the database should still/again match the pre-sync state, of
|
|
437
|
+
# no records owned by the repository.
|
|
438
|
+
self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.id).exists())
|
|
439
|
+
self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.id).exists())
|
|
440
|
+
self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.id).exists())
|
|
441
|
+
self.assertFalse(Job.objects.filter(module_name__startswith=f"{self.repo.slug}.").exists())
|
|
442
|
+
device = Device.objects.get(name=self.device.name)
|
|
443
|
+
self.assertIsNone(device.local_config_context_data)
|
|
444
|
+
self.assertIsNone(device.local_config_context_data_owner)
|
|
445
|
+
self.repo.refresh_from_db()
|
|
446
|
+
self.assertEqual(self.repo.current_head, "")
|
|
447
|
+
|
|
436
448
|
# Check for specific log messages
|
|
437
449
|
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
438
450
|
warning_logs = log_entries.filter(log_level=LogLevelChoices.LOG_WARNING)
|
|
@@ -592,6 +604,81 @@ class GitTest(TransactionTestCase):
|
|
|
592
604
|
|
|
593
605
|
self.assert_job_exists(installed=False)
|
|
594
606
|
|
|
607
|
+
def test_git_repository_sync_rollback(self):
|
|
608
|
+
"""
|
|
609
|
+
Once a "known-good" sync state is achieved, resync to a new "bad" head commit should fail and be rolled back.
|
|
610
|
+
"""
|
|
611
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
612
|
+
with self.settings(GIT_ROOT=tempdir):
|
|
613
|
+
# Initially have a successful sync to a good commit that provides data
|
|
614
|
+
self.repo.branch = "valid-files" # actually a tag
|
|
615
|
+
self.repo.save()
|
|
616
|
+
job_model = GitRepositorySync().job_model
|
|
617
|
+
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
618
|
+
job_result.refresh_from_db()
|
|
619
|
+
self.assertEqual(
|
|
620
|
+
job_result.status,
|
|
621
|
+
JobResultStatusChoices.STATUS_SUCCESS,
|
|
622
|
+
(job_result.traceback, list(job_result.job_log_entries.values_list("message", flat=True))),
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
|
|
626
|
+
self.assert_implicit_config_context_exists("Location context")
|
|
627
|
+
self.assert_config_context_schema_record_exists("Config Context Schema 1")
|
|
628
|
+
self.assert_device_exists(self.device.name)
|
|
629
|
+
self.assert_export_template_device("template.j2")
|
|
630
|
+
self.assert_export_template_html_exist("template2.html")
|
|
631
|
+
self.assert_export_template_vlan_exists("template.j2")
|
|
632
|
+
self.assert_job_exists(name="MyJob")
|
|
633
|
+
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
634
|
+
self.assert_job_exists(name="MyJobHookReceiver")
|
|
635
|
+
|
|
636
|
+
# Create JobButton and JobHook
|
|
637
|
+
JobButton.objects.create(
|
|
638
|
+
name="MyJobButton", enabled=True, text="Click me", job=Job.objects.get(name="MyJobButtonReceiver")
|
|
639
|
+
)
|
|
640
|
+
JobHook.objects.create(name="MyJobHook", enabled=True, job=Job.objects.get(name="MyJobHookReceiver"))
|
|
641
|
+
|
|
642
|
+
self.repo.refresh_from_db()
|
|
643
|
+
self.assertNotEqual(self.repo.current_head, "")
|
|
644
|
+
good_current_head = self.repo.current_head
|
|
645
|
+
|
|
646
|
+
# Now change to the `main` branch (which includes the current commit, followed by a "bad" commit)
|
|
647
|
+
self.repo.branch = "main"
|
|
648
|
+
self.repo.save()
|
|
649
|
+
|
|
650
|
+
# Resync, attempting and failing to update to the new commit
|
|
651
|
+
job_result = run_job_for_testing(job=job_model, repository=self.repo.pk)
|
|
652
|
+
job_result.refresh_from_db()
|
|
653
|
+
self.assertEqual(
|
|
654
|
+
job_result.status,
|
|
655
|
+
JobResultStatusChoices.STATUS_FAILURE,
|
|
656
|
+
job_result.result,
|
|
657
|
+
)
|
|
658
|
+
log_entries = JobLogEntry.objects.filter(job_result=job_result)
|
|
659
|
+
|
|
660
|
+
# Assert database changes were rolled back
|
|
661
|
+
self.repo.refresh_from_db()
|
|
662
|
+
try:
|
|
663
|
+
self.assertEqual(self.repo.current_head, good_current_head)
|
|
664
|
+
self.assert_explicit_config_context_exists("Frobozz 1000 NTP servers")
|
|
665
|
+
self.assert_implicit_config_context_exists("Location context")
|
|
666
|
+
self.assert_config_context_schema_record_exists("Config Context Schema 1")
|
|
667
|
+
self.assert_device_exists(self.device.name)
|
|
668
|
+
self.assert_export_template_device("template.j2")
|
|
669
|
+
self.assert_export_template_html_exist("template2.html")
|
|
670
|
+
self.assert_export_template_vlan_exists("template.j2")
|
|
671
|
+
self.assert_job_exists(name="MyJob")
|
|
672
|
+
self.assert_job_exists(name="MyJobButtonReceiver")
|
|
673
|
+
self.assert_job_exists(name="MyJobHookReceiver")
|
|
674
|
+
self.assertTrue(JobButton.objects.get(name="MyJobButton").enabled)
|
|
675
|
+
self.assertTrue(JobHook.objects.get(name="MyJobHook").enabled)
|
|
676
|
+
except Exception:
|
|
677
|
+
for log in log_entries:
|
|
678
|
+
print(log.message)
|
|
679
|
+
print(job_result.traceback)
|
|
680
|
+
raise
|
|
681
|
+
|
|
595
682
|
def test_git_dry_run(self):
|
|
596
683
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
597
684
|
with self.settings(GIT_ROOT=tempdir):
|
|
@@ -304,6 +304,10 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
304
304
|
sg.add_members(Prefix.objects.filter(ip_version=4))
|
|
305
305
|
self.assertIsInstance(sg.members, PrefixQuerySet)
|
|
306
306
|
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
|
|
307
|
+
# test cumulative construction and alternate code path
|
|
308
|
+
sg.add_members(list(Prefix.objects.filter(ip_version=6)))
|
|
309
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
|
|
310
|
+
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
|
|
307
311
|
# test duplicate objects aren't re-added
|
|
308
312
|
sg.add_members(Prefix.objects.all())
|
|
309
313
|
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
|
|
@@ -321,10 +325,18 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
321
325
|
sg.remove_members(list(Prefix.objects.filter(ip_version=4)))
|
|
322
326
|
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
323
327
|
self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
|
|
328
|
+
# test cumulative removal and alternate code path
|
|
329
|
+
sg.remove_members(list(Prefix.objects.filter(ip_version=6)))
|
|
330
|
+
self.assertQuerysetEqual(sg.members, Prefix.objects.none())
|
|
331
|
+
self.assertEqual(sg.static_group_associations.count(), 0)
|
|
324
332
|
|
|
325
333
|
# test property setter
|
|
326
334
|
sg.members = Prefix.objects.filter(ip_version=4)
|
|
327
335
|
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
|
|
336
|
+
sg.members = Prefix.objects.filter(ip_version=6)
|
|
337
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
338
|
+
sg.members = list(Prefix.objects.filter(ip_version=4))
|
|
339
|
+
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
|
|
328
340
|
sg.members = list(Prefix.objects.filter(ip_version=6))
|
|
329
341
|
self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
|
|
330
342
|
|
|
@@ -6,12 +6,15 @@ from django.core.exceptions import ValidationError
|
|
|
6
6
|
from django.template import engines
|
|
7
7
|
from django.test import override_settings
|
|
8
8
|
from django.urls import NoReverseMatch, reverse
|
|
9
|
+
import django_tables2 as tables
|
|
9
10
|
import netaddr
|
|
10
11
|
|
|
11
12
|
from nautobot.circuits.models import Circuit, CircuitType, Provider
|
|
12
13
|
from nautobot.core.testing import APIViewTestCases, disable_warnings, extract_page_body, TestCase, ViewTestCases
|
|
14
|
+
from nautobot.core.utils.lookup import get_table_for_model
|
|
13
15
|
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
14
16
|
from nautobot.dcim.tests.test_views import create_test_device
|
|
17
|
+
from nautobot.extras import plugins
|
|
15
18
|
from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
|
|
16
19
|
from nautobot.extras.jobs import get_job
|
|
17
20
|
from nautobot.extras.models import CustomField, Relationship, RelationshipAssociation, Role, Secret, Status
|
|
@@ -647,6 +650,97 @@ class FilterExtensionTest(TestCase):
|
|
|
647
650
|
self.assertIn("example_app_description", form.fields.keys())
|
|
648
651
|
|
|
649
652
|
|
|
653
|
+
class TableExtensionTest(TestCase):
|
|
654
|
+
"""Tests for adding table extensions."""
|
|
655
|
+
|
|
656
|
+
def test_alter_queryset(self):
|
|
657
|
+
"""Test the 'alter_queryset' method of the TableExtension class."""
|
|
658
|
+
extension = plugins.TableExtension()
|
|
659
|
+
queryset = object()
|
|
660
|
+
result = extension.alter_queryset(queryset)
|
|
661
|
+
self.assertEqual(result, queryset)
|
|
662
|
+
|
|
663
|
+
def test__add_columns_into_model_table(self):
|
|
664
|
+
"""Test the '_add_columns_into_model_table' function."""
|
|
665
|
+
extension = plugins.TableExtension()
|
|
666
|
+
extension.model = "tenancy.tenant"
|
|
667
|
+
extension.table_columns = {
|
|
668
|
+
"tenant_group": tables.Column(verbose_name="Name conflicts with existing column"),
|
|
669
|
+
"tenant_success": tables.Column(verbose_name="This name should be registered"),
|
|
670
|
+
}
|
|
671
|
+
extension.add_to_default_columns = ["tenant_group", "tenant_success"]
|
|
672
|
+
|
|
673
|
+
with self.assertLogs("nautobot.extras.plugins", level="WARNING") as logger:
|
|
674
|
+
plugins._add_columns_into_model_table(extension, "tenant")
|
|
675
|
+
table = get_table_for_model(extension.model)
|
|
676
|
+
expected = [
|
|
677
|
+
"ERROR:nautobot.extras.plugins:tenant: There was a name conflict with existing "
|
|
678
|
+
"table column `tenant_group`, the custom column was ignored."
|
|
679
|
+
]
|
|
680
|
+
with self.subTest("error is logged"):
|
|
681
|
+
self.assertEqual(logger.output, expected)
|
|
682
|
+
|
|
683
|
+
with self.subTest("column is added to default_columns"):
|
|
684
|
+
self.assertIn("tenant_success", table.base_columns)
|
|
685
|
+
|
|
686
|
+
def test__add_column_to_table_base_columns(self):
|
|
687
|
+
"""Test the '_add_column_to_table_base_columns' function."""
|
|
688
|
+
table = get_table_for_model("tenancy.tenant")
|
|
689
|
+
|
|
690
|
+
with self.subTest("raises TypeError"):
|
|
691
|
+
column = object()
|
|
692
|
+
with self.assertRaises(TypeError) as context:
|
|
693
|
+
plugins._add_column_to_table_base_columns(table, "test_column", column, "my_app")
|
|
694
|
+
self.assertEqual(
|
|
695
|
+
context.exception, "Custom column `test_column` is not an instance of django_tables2.Column."
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
with self.subTest("raises AttributeError"):
|
|
699
|
+
column = tables.Column()
|
|
700
|
+
with self.assertRaises(AttributeError) as context:
|
|
701
|
+
plugins._add_column_to_table_base_columns(table, "pk", column, "my_app")
|
|
702
|
+
self.assertEqual(
|
|
703
|
+
context.exception, "There was a conflict with table column `pk`, the custom column was ignored."
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
with self.subTest("Adds column to base_columns"):
|
|
707
|
+
column = tables.Column()
|
|
708
|
+
plugins._add_column_to_table_base_columns(table, "unique_name", column, "my_app")
|
|
709
|
+
self.assertIn("unique_name", table.base_columns)
|
|
710
|
+
|
|
711
|
+
def test__modify_default_table_columns(self):
|
|
712
|
+
"""Test the '_modify_default_table_columns' function."""
|
|
713
|
+
extension = plugins.TableExtension()
|
|
714
|
+
extension.model = "tenancy.tenant"
|
|
715
|
+
extension.add_to_default_columns = ["new_column"]
|
|
716
|
+
extension.remove_from_default_columns = ["description"]
|
|
717
|
+
|
|
718
|
+
table = get_table_for_model(extension.model)
|
|
719
|
+
table.base_columns["new_column"] = tables.Column()
|
|
720
|
+
|
|
721
|
+
plugins._modify_default_table_columns(extension, "my_app")
|
|
722
|
+
|
|
723
|
+
with self.subTest("column is added to default_columns"):
|
|
724
|
+
self.assertIn("new_column", table.Meta.default_columns)
|
|
725
|
+
|
|
726
|
+
with self.subTest("column is removed from default_columns"):
|
|
727
|
+
self.assertNotIn("description", table.Meta.default_columns)
|
|
728
|
+
|
|
729
|
+
def test__validate_is_subclass_of_table_extension(self):
|
|
730
|
+
"""Test the '_validate_is_subclass_of_table_extension' function."""
|
|
731
|
+
with self.assertRaises(TypeError):
|
|
732
|
+
plugins._validate_is_subclass_of_table_extension(object)
|
|
733
|
+
|
|
734
|
+
def test__validate_table_column_name_is_prefixed_with_app_name(self):
|
|
735
|
+
"""Test the '_validate_table_column_name_is_prefixed_with_app_name' function."""
|
|
736
|
+
with self.assertRaises(ValueError) as context:
|
|
737
|
+
plugins._validate_table_column_name_is_prefixed_with_app_name("not_prefixed", "prefix")
|
|
738
|
+
self.assertEqual(
|
|
739
|
+
context.exception,
|
|
740
|
+
"Attempted to create a custom table column `not_prefixed` that did not start with `prefix`",
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
|
|
650
744
|
class LoadPluginTest(TestCase):
|
|
651
745
|
"""
|
|
652
746
|
Validate that plugin helpers work as intended.
|
nautobot/extras/views.py
CHANGED
|
@@ -737,7 +737,9 @@ class DynamicGroupView(generic.ObjectView):
|
|
|
737
737
|
|
|
738
738
|
if table_class is not None:
|
|
739
739
|
# Members table (for display on Members nav tab)
|
|
740
|
-
members_table = table_class(
|
|
740
|
+
members_table = table_class(
|
|
741
|
+
members.restrict(request.user, "view"), orderable=False, exclude=["dynamic_group_count"]
|
|
742
|
+
)
|
|
741
743
|
paginate = {
|
|
742
744
|
"paginator_class": EnhancedPaginator,
|
|
743
745
|
"per_page": get_paginate_count(request),
|
nautobot/ipam/filters.py
CHANGED
|
@@ -586,13 +586,13 @@ class VLANFilterSet(
|
|
|
586
586
|
fields = ["id", "name", "tags", "vid"]
|
|
587
587
|
|
|
588
588
|
def get_for_device(self, queryset, name, value):
|
|
589
|
-
# TODO: after Location model replaced Site, which was not a hierarchical model, should we consider to include
|
|
590
|
-
# VLANs that belong to the parent/child locations of the `device.location`?
|
|
591
589
|
"""Return all VLANs available to the specified Device(value)."""
|
|
592
590
|
devices = Device.objects.select_related("location").filter(**{f"{name}__in": value})
|
|
593
591
|
if not devices.exists():
|
|
594
592
|
return queryset.none()
|
|
595
593
|
location_ids = list(devices.values_list("location__id", flat=True))
|
|
594
|
+
for location in Location.objects.filter(pk__in=location_ids):
|
|
595
|
+
location_ids.extend([ancestor.id for ancestor in location.ancestors()])
|
|
596
596
|
return queryset.filter(Q(locations__isnull=True) | Q(locations__in=location_ids))
|
|
597
597
|
|
|
598
598
|
|
nautobot/ipam/models.py
CHANGED
|
@@ -890,12 +890,39 @@ class Prefix(PrimaryModel):
|
|
|
890
890
|
)
|
|
891
891
|
return available_ips
|
|
892
892
|
|
|
893
|
+
def get_child_ips(self):
|
|
894
|
+
"""
|
|
895
|
+
Return IP addresses with this prefix as their *direct* parent.
|
|
896
|
+
|
|
897
|
+
Does *not* include IPs that descend from a descendant prefix; if those are desired, use get_all_ips() instead.
|
|
898
|
+
|
|
899
|
+
```
|
|
900
|
+
Prefix 10.0.0.0/16
|
|
901
|
+
IPAddress 10.0.0.1/24
|
|
902
|
+
Prefix 10.0.1.0/24
|
|
903
|
+
IPAddress 10.0.1.1/24
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
In the above example, `<Prefix 10.0.0.0/16>.get_child_ips()` will *only* return 10.0.0.1/24,
|
|
907
|
+
while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
|
|
908
|
+
"""
|
|
909
|
+
return self.ip_addresses.all()
|
|
910
|
+
|
|
893
911
|
def get_all_ips(self):
|
|
894
912
|
"""
|
|
895
913
|
Return all IP addresses contained within this prefix, including child prefixes' IP addresses.
|
|
896
914
|
|
|
897
|
-
|
|
898
|
-
|
|
915
|
+
This is distinct from the behavior of `get_child_ips()` and in *most* cases is probably preferred.
|
|
916
|
+
|
|
917
|
+
```
|
|
918
|
+
Prefix 10.0.0.0/16
|
|
919
|
+
IPAddress 10.0.0.1/24
|
|
920
|
+
Prefix 10.0.1.0/24
|
|
921
|
+
IPAddress 10.0.1.1/24
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
In the above example, `<Prefix 10.0.0.0/16>.get_child_ips()` will *only* return 10.0.0.1/24,
|
|
925
|
+
while `<Prefix 10.0.0.0/16>.get_all_ips()` will return *both* 10.0.0.1.24 and 10.0.1.1/24.
|
|
899
926
|
"""
|
|
900
927
|
return IPAddress.objects.filter(
|
|
901
928
|
parent__namespace=self.namespace, host__gte=self.network, host__lte=self.broadcast
|
|
@@ -150,9 +150,9 @@
|
|
|
150
150
|
</tr>
|
|
151
151
|
</table>
|
|
152
152
|
</div>
|
|
153
|
-
{% include 'panel_table.html' with table=parent_prefixes_table heading='Parent Prefixes' %}
|
|
154
153
|
{% endblock content_right_page %}
|
|
155
154
|
|
|
156
155
|
{% block content_full_width_page %}
|
|
157
|
-
|
|
156
|
+
{% include 'utilities/obj_table.html' with table=parent_prefixes_table table_template='panel_table.html' heading='Parent Prefixes' %}
|
|
157
|
+
{% include 'utilities/obj_table.html' with table=related_ips_table table_template='panel_table.html' heading='Related IP Addresses' %}
|
|
158
158
|
{% endblock content_full_width_page %}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
{% render_table interface_table 'inc/table.html' %}
|
|
22
22
|
</div>
|
|
23
23
|
</form>
|
|
24
|
+
{% if interface_table.paginator.num_pages > 1 %}
|
|
25
|
+
{% include "inc/paginator.html" with paginator=interface_table.paginator page=interface_table.page %}
|
|
26
|
+
{% endif %}
|
|
24
27
|
{% table_config_form interface_table %}
|
|
25
28
|
{% endblock content %}
|
|
26
29
|
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
{% render_table vm_interface_table 'inc/table.html' %}
|
|
22
22
|
</div>
|
|
23
23
|
</form>
|
|
24
|
+
{% if vm_interface_table.paginator.num_pages > 1 %}
|
|
25
|
+
{% include "inc/paginator.html" with paginator=vm_interface_table.paginator page=vm_interface_table.page %}
|
|
26
|
+
{% endif %}
|
|
24
27
|
{% table_config_form vm_interface_table %}
|
|
25
28
|
{% endblock content %}
|
|
26
29
|
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
{% endblock content_left_page %}
|
|
118
118
|
|
|
119
119
|
{% block content_right_page %}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
{% include "utilities/obj_table.html" with table=parent_prefix_table table_template="panel_table.html" heading="Parent Prefixes" %}
|
|
121
|
+
{% include "utilities/obj_table.html" with table=vrf_table table_template="panel_table.html" heading="Assigned VRFs" %}
|
|
122
|
+
{% include "utilities/obj_table.html" with table=cloud_network_table table_template="panel_table.html" heading="Assigned Cloud Networks" %}
|
|
123
123
|
{% endblock content_right_page %}
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
{% endblock content_left_page %}
|
|
25
25
|
|
|
26
26
|
{% block content_right_page %}
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
{% include 'utilities/obj_table.html' with table=importing_vrfs_table table_template='panel_table.html' heading="Importing VRFs" %}
|
|
28
|
+
{% include 'utilities/obj_table.html' with table=exporting_vrfs_table table_template='panel_table.html' heading="Exporting VRFs" %}
|
|
29
29
|
{% endblock content_right_page %}
|
|
@@ -38,8 +38,11 @@
|
|
|
38
38
|
{% endblock content_left_page %}
|
|
39
39
|
|
|
40
40
|
{% block content_right_page %}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{% include 'panel_table.html' with table=prefix_table heading="Assigned Prefixes" %}
|
|
44
|
-
{% include 'panel_table.html' with table=device_table heading="Assigned Devices" %}
|
|
41
|
+
{% include 'utilities/obj_table.html' with table=import_targets_table table_template='panel_table.html' heading="Import Route Targets" %}
|
|
42
|
+
{% include 'utilities/obj_table.html' with table=export_targets_table table_template='panel_table.html' heading="Export Route Targets" %}
|
|
45
43
|
{% endblock content_right_page %}
|
|
44
|
+
|
|
45
|
+
{% block content_full_width_page %}
|
|
46
|
+
{% include 'utilities/obj_table.html' with table=prefix_table table_template='panel_table.html' heading="Assigned Prefixes" %}
|
|
47
|
+
{% include 'utilities/obj_table.html' with table=device_table table_template='panel_table.html' heading="Assigned Devices" %}
|
|
48
|
+
{% endblock content_full_width_page %}
|