nautobot 2.1.7__py3-none-any.whl → 2.1.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/api.py +1 -2
- nautobot/apps/utils.py +4 -0
- nautobot/apps/views.py +2 -0
- nautobot/circuits/api/urls.py +1 -2
- nautobot/circuits/api/views.py +0 -12
- nautobot/circuits/tests/integration/test_relationships.py +0 -4
- nautobot/core/api/routers.py +25 -3
- nautobot/core/api/utils.py +4 -0
- nautobot/core/api/views.py +21 -15
- nautobot/core/celery/schedulers.py +13 -0
- nautobot/core/choices.py +0 -21
- nautobot/core/models/__init__.py +1 -1
- nautobot/core/models/tree_queries.py +29 -7
- nautobot/core/releases.py +1 -1
- nautobot/core/settings.py +9 -0
- nautobot/core/settings_funcs.py +0 -18
- nautobot/core/signals.py +5 -5
- nautobot/core/tasks.py +7 -3
- nautobot/core/templates/admin/base.html +23 -94
- nautobot/core/templates/generic/object_list.html +2 -0
- nautobot/core/templates/graphene/graphiql.html +18 -47
- nautobot/core/templates/inc/footer.html +5 -5
- nautobot/core/templates/inc/nav_menu.html +0 -7
- nautobot/core/templates/nautobot_config.py.j2 +6 -0
- nautobot/core/templates/rest_framework/api.html +12 -5
- nautobot/core/testing/mixins.py +13 -5
- nautobot/core/tests/integration/test_plugin_navbar.py +7 -21
- nautobot/core/tests/integration/test_view_authentication.py +67 -0
- nautobot/core/tests/runner.py +25 -2
- nautobot/core/tests/test_graphql.py +2 -14
- nautobot/core/tests/test_models.py +3 -3
- nautobot/core/tests/test_navigations.py +67 -10
- nautobot/core/tests/test_releases.py +9 -3
- nautobot/core/tests/test_views.py +23 -16
- nautobot/core/utils/lookup.py +124 -0
- nautobot/core/views/__init__.py +3 -7
- nautobot/core/views/generic.py +9 -0
- nautobot/dcim/api/urls.py +1 -2
- nautobot/dcim/api/views.py +1 -12
- nautobot/dcim/choices.py +56 -0
- nautobot/dcim/models/racks.py +1 -3
- nautobot/dcim/navigation.py +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +67 -43
- nautobot/dcim/tests/test_api.py +3 -0
- nautobot/dcim/tests/test_filters.py +0 -28
- nautobot/dcim/views.py +5 -2
- nautobot/extras/api/urls.py +1 -2
- nautobot/extras/api/views.py +0 -10
- nautobot/extras/choices.py +14 -0
- nautobot/extras/models/customfields.py +93 -34
- nautobot/extras/models/groups.py +1 -1
- nautobot/extras/models/relationships.py +32 -19
- nautobot/extras/navigation.py +3 -2
- nautobot/extras/plugins/__init__.py +8 -0
- nautobot/extras/plugins/views.py +6 -9
- nautobot/extras/querysets.py +1 -1
- nautobot/extras/signals.py +12 -6
- nautobot/extras/templates/extras/customfield.html +22 -14
- nautobot/extras/templatetags/job_buttons.py +7 -0
- nautobot/extras/templatetags/plugins.py +5 -1
- nautobot/extras/tests/test_customfields.py +323 -287
- nautobot/extras/tests/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_jobs.py +2 -2
- nautobot/extras/tests/test_plugins.py +41 -0
- nautobot/extras/tests/test_relationships.py +31 -14
- nautobot/extras/tests/test_views.py +124 -1
- nautobot/extras/utils.py +7 -3
- nautobot/extras/views.py +10 -10
- nautobot/ipam/api/urls.py +1 -2
- nautobot/ipam/api/views.py +6 -13
- nautobot/ipam/tables.py +0 -1
- nautobot/ipam/tests/test_graphql.py +2 -3
- nautobot/ipam/views.py +12 -10
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +30 -2
- nautobot/project-static/docs/apps/index.html +30 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +410 -410
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +386 -358
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +45 -17
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +759 -602
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +528 -467
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +205 -109
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +30 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +1265 -785
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1827 -1746
- nautobot/project-static/docs/development/apps/api/configuration-view.html +30 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +30 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +31 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +30 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +30 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +30 -2
- nautobot/project-static/docs/development/apps/api/setup.html +30 -2
- nautobot/project-static/docs/development/apps/api/testing.html +33 -5
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +33 -5
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-detail-views.html +13 -5559
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +5594 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/tabs.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +44 -11
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +47 -14
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +30 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +30 -2
- nautobot/project-static/docs/development/apps/index.html +30 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +30 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +30 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +30 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +30 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +30 -2
- nautobot/project-static/docs/development/core/application-registry.html +30 -2
- nautobot/project-static/docs/development/core/best-practices.html +33 -5
- nautobot/project-static/docs/development/core/bootstrap-ui.html +30 -2
- nautobot/project-static/docs/development/core/caching.html +5481 -0
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +30 -2
- nautobot/project-static/docs/development/core/extending-models.html +33 -5
- nautobot/project-static/docs/development/core/generic-views.html +30 -2
- nautobot/project-static/docs/development/core/getting-started.html +49 -12
- nautobot/project-static/docs/development/core/homepage.html +30 -2
- nautobot/project-static/docs/development/core/index.html +30 -2
- nautobot/project-static/docs/development/core/model-features.html +30 -2
- nautobot/project-static/docs/development/core/natural-keys.html +30 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +30 -2
- nautobot/project-static/docs/development/core/release-checklist.html +30 -2
- nautobot/project-static/docs/development/core/role-internals.html +30 -2
- nautobot/project-static/docs/development/core/style-guide.html +30 -2
- nautobot/project-static/docs/development/core/templates.html +30 -2
- nautobot/project-static/docs/development/core/testing.html +30 -2
- nautobot/project-static/docs/development/core/user-preferences.html +30 -2
- nautobot/project-static/docs/development/index.html +30 -2
- nautobot/project-static/docs/development/jobs/index.html +30 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +30 -2
- nautobot/project-static/docs/index.html +30 -2
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/index.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +31 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +30 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +573 -134
- nautobot/project-static/docs/release-notes/version-2.0.html +30 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +539 -170
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +250 -240
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +49 -2
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +30 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +30 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +37 -5
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +6019 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +30 -2
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +33 -5
- nautobot/project-static/docs/user-guide/administration/installation/services.html +30 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +30 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +30 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +30 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +30 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +30 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +33 -5
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +30 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +30 -2
- nautobot/project-static/docs/user-guide/index.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +111 -15
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +30 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +30 -2
- nautobot/tenancy/api/urls.py +1 -2
- nautobot/tenancy/api/views.py +0 -12
- nautobot/tenancy/navigation.py +1 -1
- nautobot/tenancy/tests/test_filters.py +0 -168
- nautobot/users/api/urls.py +1 -2
- nautobot/users/api/views.py +2 -65
- nautobot/users/views.py +8 -8
- nautobot/virtualization/api/urls.py +1 -2
- nautobot/virtualization/api/views.py +0 -12
- nautobot/virtualization/tests/test_filters.py +0 -28
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/METADATA +2 -2
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/RECORD +338 -334
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/NOTICE +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/WHEEL +0 -0
- {nautobot-2.1.7.dist-info → nautobot-2.1.9.dist-info}/entry_points.txt +0 -0
|
@@ -1016,7 +1016,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
|
|
|
1016
1016
|
self.assertEqual(mock_get_queryset.call_count, 2)
|
|
1017
1017
|
|
|
1018
1018
|
# Clean-up after ourselves
|
|
1019
|
-
cache.delete(
|
|
1019
|
+
cache.delete(group.members_cache_key)
|
|
1020
1020
|
|
|
1021
1021
|
@override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=0)
|
|
1022
1022
|
def test_member_caching_disabled(self):
|
|
@@ -10,6 +10,7 @@ import uuid
|
|
|
10
10
|
from constance.test import override_config
|
|
11
11
|
from django.conf import settings
|
|
12
12
|
from django.contrib.contenttypes.models import ContentType
|
|
13
|
+
from django.core.cache import cache
|
|
13
14
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
14
15
|
from django.core.management import call_command
|
|
15
16
|
from django.core.management.base import CommandError
|
|
@@ -34,7 +35,6 @@ from nautobot.extras.choices import (
|
|
|
34
35
|
)
|
|
35
36
|
from nautobot.extras.context_managers import change_logging, JobHookChangeContext, web_request_context
|
|
36
37
|
from nautobot.extras.jobs import get_job
|
|
37
|
-
from nautobot.extras.utils import change_logged_models_queryset
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class JobTest(TestCase):
|
|
@@ -928,7 +928,7 @@ class JobHookTransactionTest(TransactionTestCase): # TODO: BaseModelTestCase mi
|
|
|
928
928
|
# We need to explicitly clear it here to have tests pass.
|
|
929
929
|
# This is not a problem during normal operation of Nautobot because content-types don't normally get deleted
|
|
930
930
|
# and recreated while Nautobot is running.
|
|
931
|
-
|
|
931
|
+
cache.delete("nautobot.extras.utils.change_logged_models_queryset")
|
|
932
932
|
|
|
933
933
|
module = "job_hook_receiver"
|
|
934
934
|
name = "TestJobHookReceiverLog"
|
|
@@ -740,3 +740,44 @@ class TestPluginCoreViewOverrides(TestCase):
|
|
|
740
740
|
),
|
|
741
741
|
response.content,
|
|
742
742
|
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@skipIf(
|
|
746
|
+
"example_plugin" not in settings.PLUGINS,
|
|
747
|
+
"example_plugin not in settings.PLUGINS",
|
|
748
|
+
)
|
|
749
|
+
class PluginTemplateExtensionsTest(TestCase):
|
|
750
|
+
"""
|
|
751
|
+
Test that registered TemplateExtensions inject content as expected
|
|
752
|
+
"""
|
|
753
|
+
|
|
754
|
+
def setUp(self):
|
|
755
|
+
super().setUp()
|
|
756
|
+
self.location = Location.objects.first()
|
|
757
|
+
self.user.is_superuser = True
|
|
758
|
+
self.user.save()
|
|
759
|
+
|
|
760
|
+
def test_list_view_buttons(self):
|
|
761
|
+
response = self.client.get(reverse("dcim:location_list"))
|
|
762
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
763
|
+
self.assertIn("LOCATION CONTENT - BUTTONS LIST", response_body, msg=response_body)
|
|
764
|
+
|
|
765
|
+
def test_detail_view_buttons(self):
|
|
766
|
+
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
767
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
768
|
+
self.assertIn("LOCATION CONTENT - BUTTONS", response_body, msg=response_body)
|
|
769
|
+
|
|
770
|
+
def test_detail_view_left_page(self):
|
|
771
|
+
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
772
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
773
|
+
self.assertIn("LOCATION CONTENT - LEFT PAGE", response_body, msg=response_body)
|
|
774
|
+
|
|
775
|
+
def test_detail_view_right_page(self):
|
|
776
|
+
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
777
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
778
|
+
self.assertIn("LOCATION CONTENT - RIGHT PAGE", response_body, msg=response_body)
|
|
779
|
+
|
|
780
|
+
def test_detail_view_full_width_page(self):
|
|
781
|
+
response = self.client.get(reverse("dcim:location", kwargs={"pk": self.location.pk}))
|
|
782
|
+
response_body = extract_page_body(response.content.decode(response.charset))
|
|
783
|
+
self.assertIn("LOCATION CONTENT - FULL WIDTH PAGE", response_body, msg=response_body)
|
|
@@ -428,19 +428,31 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
428
428
|
str(error.exception),
|
|
429
429
|
)
|
|
430
430
|
|
|
431
|
-
def
|
|
432
|
-
"""Test that the
|
|
431
|
+
def test_get_for_model_caching_and_cache_invalidation(self):
|
|
432
|
+
"""Test that the cache is used and is properly invalidated when Relationships are created or deleted."""
|
|
433
433
|
|
|
434
434
|
manager = Relationship.objects
|
|
435
|
-
manager_methods =
|
|
435
|
+
manager_methods = [
|
|
436
|
+
(manager.get_for_model, 2),
|
|
437
|
+
(manager.get_for_model_source, 1),
|
|
438
|
+
(manager.get_for_model_destination, 1),
|
|
439
|
+
]
|
|
436
440
|
|
|
437
|
-
for manager_method in manager_methods:
|
|
441
|
+
for manager_method, expected_queries in manager_methods:
|
|
438
442
|
with self.subTest(manager_method=manager_method.__name__):
|
|
439
|
-
|
|
443
|
+
manager_method(Location)
|
|
440
444
|
|
|
441
445
|
# Assert that the cache is used when calling method a second time
|
|
442
|
-
|
|
443
|
-
|
|
446
|
+
with self.assertNumQueries(0):
|
|
447
|
+
manager_method(Location)
|
|
448
|
+
|
|
449
|
+
# Assert that different models are cached separately
|
|
450
|
+
with self.assertNumQueries(expected_queries):
|
|
451
|
+
manager_method(Rack)
|
|
452
|
+
with self.assertNumQueries(0):
|
|
453
|
+
manager_method(Rack)
|
|
454
|
+
with self.assertNumQueries(0):
|
|
455
|
+
manager_method(Location)
|
|
444
456
|
|
|
445
457
|
# Assert that the cache is invalidated on object save
|
|
446
458
|
relationship = Relationship(
|
|
@@ -453,13 +465,18 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
453
465
|
type=RelationshipTypeChoices.TYPE_MANY_TO_MANY,
|
|
454
466
|
)
|
|
455
467
|
relationship.save()
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
468
|
+
try:
|
|
469
|
+
with self.assertNumQueries(expected_queries):
|
|
470
|
+
manager_method(Location)
|
|
471
|
+
with self.assertNumQueries(0):
|
|
472
|
+
manager_method(Location)
|
|
473
|
+
finally:
|
|
474
|
+
# Assert that the cache is invalidated on object delete
|
|
475
|
+
relationship.delete()
|
|
476
|
+
with self.assertNumQueries(expected_queries):
|
|
477
|
+
manager_method(Location)
|
|
478
|
+
with self.assertNumQueries(0):
|
|
479
|
+
manager_method(Location)
|
|
463
480
|
|
|
464
481
|
|
|
465
482
|
class RelationshipAssociationTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
@@ -3,6 +3,7 @@ from unittest import mock
|
|
|
3
3
|
import urllib.parse
|
|
4
4
|
import uuid
|
|
5
5
|
|
|
6
|
+
from django.conf import settings
|
|
6
7
|
from django.contrib.auth import get_user_model
|
|
7
8
|
from django.contrib.contenttypes.models import ContentType
|
|
8
9
|
from django.core.exceptions import ValidationError
|
|
@@ -20,6 +21,7 @@ from nautobot.dcim.tests import test_views
|
|
|
20
21
|
from nautobot.extras.choices import (
|
|
21
22
|
CustomFieldTypeChoices,
|
|
22
23
|
JobExecutionType,
|
|
24
|
+
LogLevelChoices,
|
|
23
25
|
ObjectChangeActionChoices,
|
|
24
26
|
SecretsGroupAccessTypeChoices,
|
|
25
27
|
SecretsGroupSecretTypeChoices,
|
|
@@ -39,6 +41,7 @@ from nautobot.extras.models import (
|
|
|
39
41
|
GraphQLQuery,
|
|
40
42
|
Job,
|
|
41
43
|
JobButton,
|
|
44
|
+
JobLogEntry,
|
|
42
45
|
JobResult,
|
|
43
46
|
Note,
|
|
44
47
|
ObjectChange,
|
|
@@ -616,6 +619,49 @@ class DynamicGroupTestCase(
|
|
|
616
619
|
"dynamic_group_memberships-MAX_NUM_FORMS": "1000",
|
|
617
620
|
}
|
|
618
621
|
|
|
622
|
+
def test_get_object_dynamic_groups_anonymous(self):
|
|
623
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
624
|
+
self.client.logout()
|
|
625
|
+
response = self.client.get(url, follow=True)
|
|
626
|
+
self.assertHttpStatus(response, 200)
|
|
627
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
628
|
+
|
|
629
|
+
def test_get_object_dynamic_groups_without_permission(self):
|
|
630
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
631
|
+
response = self.client.get(url)
|
|
632
|
+
self.assertHttpStatus(response, [403, 404])
|
|
633
|
+
|
|
634
|
+
def test_get_object_dynamic_groups_with_permission(self):
|
|
635
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
636
|
+
self.add_permissions("dcim.view_device", "extras.view_dynamicgroup")
|
|
637
|
+
response = self.client.get(url)
|
|
638
|
+
self.assertHttpStatus(response, 200)
|
|
639
|
+
response_body = response.content.decode(response.charset)
|
|
640
|
+
self.assertIn("DG 1", response_body, msg=response_body)
|
|
641
|
+
self.assertIn("DG 2", response_body, msg=response_body)
|
|
642
|
+
self.assertIn("DG 3", response_body, msg=response_body)
|
|
643
|
+
|
|
644
|
+
def test_get_object_dynamic_groups_with_constrained_permission(self):
|
|
645
|
+
self.add_permissions("extras.view_dynamicgroup")
|
|
646
|
+
obj_perm = ObjectPermission(
|
|
647
|
+
name="View a device",
|
|
648
|
+
constraints={"pk": Device.objects.first().pk},
|
|
649
|
+
actions=["view"],
|
|
650
|
+
)
|
|
651
|
+
obj_perm.save()
|
|
652
|
+
obj_perm.users.add(self.user)
|
|
653
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
|
|
654
|
+
|
|
655
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
656
|
+
response = self.client.get(url)
|
|
657
|
+
self.assertHttpStatus(response, 200)
|
|
658
|
+
response_body = response.content.decode(response.charset)
|
|
659
|
+
self.assertIn("DG 1", response_body, msg=response_body)
|
|
660
|
+
|
|
661
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.last().pk})
|
|
662
|
+
response = self.client.get(url)
|
|
663
|
+
self.assertHttpStatus(response, 404)
|
|
664
|
+
|
|
619
665
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
620
666
|
def test_edit_saved_filter(self):
|
|
621
667
|
"""Test that editing a filter works using the edit view."""
|
|
@@ -785,6 +831,34 @@ class GitRepositoryTestCase(
|
|
|
785
831
|
self.form_data = form_data
|
|
786
832
|
super().test_edit_object_with_constrained_permission()
|
|
787
833
|
|
|
834
|
+
def test_post_sync_repo_anonymous(self):
|
|
835
|
+
self.client.logout()
|
|
836
|
+
url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
|
|
837
|
+
response = self.client.post(url, follow=True)
|
|
838
|
+
self.assertHttpStatus(response, 200)
|
|
839
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
840
|
+
|
|
841
|
+
def test_post_sync_repo_without_permission(self):
|
|
842
|
+
url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
|
|
843
|
+
response = self.client.post(url)
|
|
844
|
+
self.assertHttpStatus(response, [403, 404])
|
|
845
|
+
|
|
846
|
+
# TODO: mock/stub out `enqueue_pull_git_repository_and_refresh_data` and test successful POST with permissions
|
|
847
|
+
|
|
848
|
+
def test_post_dryrun_repo_anonymous(self):
|
|
849
|
+
self.client.logout()
|
|
850
|
+
url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
|
|
851
|
+
response = self.client.post(url, follow=True)
|
|
852
|
+
self.assertHttpStatus(response, 200)
|
|
853
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
854
|
+
|
|
855
|
+
def test_post_dryrun_repo_without_permission(self):
|
|
856
|
+
url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
|
|
857
|
+
response = self.client.post(url)
|
|
858
|
+
self.assertHttpStatus(response, [403, 404])
|
|
859
|
+
|
|
860
|
+
# TODO: mock/stub out `enqueue_git_repository_diff_origin_and_local` and test successful POST with permissions
|
|
861
|
+
|
|
788
862
|
|
|
789
863
|
class NoteTestCase(
|
|
790
864
|
ViewTestCases.CreateObjectViewTestCase,
|
|
@@ -1502,6 +1576,34 @@ class JobResultTestCase(
|
|
|
1502
1576
|
def setUpTestData(cls):
|
|
1503
1577
|
JobResult.objects.create(name="pass.TestPass")
|
|
1504
1578
|
JobResult.objects.create(name="fail.TestFail")
|
|
1579
|
+
JobLogEntry.objects.create(
|
|
1580
|
+
log_level=LogLevelChoices.LOG_INFO,
|
|
1581
|
+
job_result=JobResult.objects.first(),
|
|
1582
|
+
grouping="run",
|
|
1583
|
+
message="This is a test",
|
|
1584
|
+
)
|
|
1585
|
+
|
|
1586
|
+
def test_get_joblogentrytable_anonymous(self):
|
|
1587
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1588
|
+
self.client.logout()
|
|
1589
|
+
response = self.client.get(url, follow=True)
|
|
1590
|
+
self.assertHttpStatus(response, 200)
|
|
1591
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
1592
|
+
|
|
1593
|
+
def test_get_joblogentrytable_without_permission(self):
|
|
1594
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1595
|
+
response = self.client.get(url)
|
|
1596
|
+
self.assertHttpStatus(response, [403, 404])
|
|
1597
|
+
|
|
1598
|
+
def test_get_joblogentrytable_with_permission(self):
|
|
1599
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1600
|
+
self.add_permissions("extras.view_jobresult", "extras.view_joblogentry")
|
|
1601
|
+
response = self.client.get(url)
|
|
1602
|
+
self.assertHttpStatus(response, 200)
|
|
1603
|
+
response_body = response.content.decode(response.charset)
|
|
1604
|
+
self.assertIn("This is a test", response_body)
|
|
1605
|
+
|
|
1606
|
+
# TODO test with constrained permissions on both JobResult and JobLogEntry records
|
|
1505
1607
|
|
|
1506
1608
|
|
|
1507
1609
|
class JobTestCase(
|
|
@@ -2019,10 +2121,11 @@ class JobButtonRenderingTestCase(TestCase):
|
|
|
2019
2121
|
|
|
2020
2122
|
def setUp(self):
|
|
2021
2123
|
super().setUp()
|
|
2124
|
+
self.job = Job.objects.get(job_class_name="TestJobButtonReceiverSimple")
|
|
2022
2125
|
self.job_button_1 = JobButton(
|
|
2023
2126
|
name="JobButton 1",
|
|
2024
2127
|
text="JobButton {{ obj.name }}",
|
|
2025
|
-
job=
|
|
2128
|
+
job=self.job,
|
|
2026
2129
|
confirmation=False,
|
|
2027
2130
|
)
|
|
2028
2131
|
self.job_button_1.validated_save()
|
|
@@ -2047,6 +2150,26 @@ class JobButtonRenderingTestCase(TestCase):
|
|
|
2047
2150
|
self.assertIn(f"JobButton {self.location_type.name}", content, content)
|
|
2048
2151
|
self.assertIn("Click me!", content, content)
|
|
2049
2152
|
|
|
2153
|
+
def test_task_queue_hidden_input_is_present(self):
|
|
2154
|
+
"""
|
|
2155
|
+
Ensure that the job button respects the job class' task_queues and the job class task_queues[0]/default is passed as a hidden form input.
|
|
2156
|
+
"""
|
|
2157
|
+
self.job.task_queues_override = True
|
|
2158
|
+
self.job.task_queues = ["overriden_queue", "default", "priority"]
|
|
2159
|
+
self.job.save()
|
|
2160
|
+
response = self.client.get(self.location_type.get_absolute_url(), follow=True)
|
|
2161
|
+
self.assertEqual(response.status_code, 200)
|
|
2162
|
+
content = extract_page_body(response.content.decode(response.charset))
|
|
2163
|
+
self.assertIn(f'<input type="hidden" name="_task_queue" value="{self.job.task_queues[0]}">', content, content)
|
|
2164
|
+
self.job.task_queues_override = False
|
|
2165
|
+
self.job.save()
|
|
2166
|
+
response = self.client.get(self.location_type.get_absolute_url(), follow=True)
|
|
2167
|
+
self.assertEqual(response.status_code, 200)
|
|
2168
|
+
content = extract_page_body(response.content.decode(response.charset))
|
|
2169
|
+
self.assertIn(
|
|
2170
|
+
f'<input type="hidden" name="_task_queue" value="{settings.CELERY_TASK_DEFAULT_QUEUE}">', content, content
|
|
2171
|
+
)
|
|
2172
|
+
|
|
2050
2173
|
def test_view_object_with_unsafe_text(self):
|
|
2051
2174
|
"""Ensure that JobButton text can't be used as a vector for XSS."""
|
|
2052
2175
|
self.job_button_1.text = '<script>alert("Hello world!")</script>'
|
nautobot/extras/utils.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import collections
|
|
2
|
-
from functools import lru_cache
|
|
3
2
|
import hashlib
|
|
4
3
|
import hmac
|
|
5
4
|
import logging
|
|
@@ -9,6 +8,7 @@ import sys
|
|
|
9
8
|
from django.apps import apps
|
|
10
9
|
from django.conf import settings
|
|
11
10
|
from django.contrib.contenttypes.models import ContentType
|
|
11
|
+
from django.core.cache import cache
|
|
12
12
|
from django.core.validators import ValidationError
|
|
13
13
|
from django.db import transaction
|
|
14
14
|
from django.db.models import Q
|
|
@@ -104,12 +104,16 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
|
|
|
104
104
|
return [_class for _class in apps.get_models() if hasattr(_class, "to_objectchange")]
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
@lru_cache(maxsize=None)
|
|
108
107
|
def change_logged_models_queryset():
|
|
109
108
|
"""
|
|
110
109
|
Cacheable function for cases where we need this queryset many times, such as when saving multiple objects.
|
|
111
110
|
"""
|
|
112
|
-
|
|
111
|
+
cache_key = "nautobot.extras.utils.change_logged_models_queryset"
|
|
112
|
+
queryset = cache.get(cache_key)
|
|
113
|
+
if queryset is None:
|
|
114
|
+
queryset = ChangeLoggedModelsQuery().as_queryset()
|
|
115
|
+
cache.set(cache_key, queryset)
|
|
116
|
+
return queryset
|
|
113
117
|
|
|
114
118
|
|
|
115
119
|
@deconstructible
|
nautobot/extras/views.py
CHANGED
|
@@ -713,7 +713,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
713
713
|
filterset = filters.DynamicGroupFilterSet
|
|
714
714
|
|
|
715
715
|
|
|
716
|
-
class ObjectDynamicGroupsView(
|
|
716
|
+
class ObjectDynamicGroupsView(generic.GenericView):
|
|
717
717
|
"""
|
|
718
718
|
Present a list of dynamic groups associated to a particular object.
|
|
719
719
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -912,18 +912,18 @@ def check_and_call_git_repository_function(request, pk, func):
|
|
|
912
912
|
messages.error(request, "Unable to run job: Celery worker process not running.")
|
|
913
913
|
return redirect(request.get_full_path(), permanent=False)
|
|
914
914
|
else:
|
|
915
|
-
repository = get_object_or_404(GitRepository, pk=pk)
|
|
915
|
+
repository = get_object_or_404(GitRepository.objects.restrict(request.user, "change"), pk=pk)
|
|
916
916
|
job_result = func(repository, request.user)
|
|
917
917
|
|
|
918
918
|
return redirect(job_result.get_absolute_url())
|
|
919
919
|
|
|
920
920
|
|
|
921
|
-
class GitRepositorySyncView(
|
|
921
|
+
class GitRepositorySyncView(generic.GenericView):
|
|
922
922
|
def post(self, request, pk):
|
|
923
923
|
return check_and_call_git_repository_function(request, pk, enqueue_pull_git_repository_and_refresh_data)
|
|
924
924
|
|
|
925
925
|
|
|
926
|
-
class GitRepositoryDryRunView(
|
|
926
|
+
class GitRepositoryDryRunView(generic.GenericView):
|
|
927
927
|
def post(self, request, pk):
|
|
928
928
|
return check_and_call_git_repository_function(request, pk, enqueue_git_repository_diff_origin_and_local)
|
|
929
929
|
|
|
@@ -1559,7 +1559,7 @@ class JobResultView(generic.ObjectView):
|
|
|
1559
1559
|
}
|
|
1560
1560
|
|
|
1561
1561
|
|
|
1562
|
-
class JobLogEntryTableView(
|
|
1562
|
+
class JobLogEntryTableView(generic.GenericView):
|
|
1563
1563
|
"""
|
|
1564
1564
|
Display a table of `JobLogEntry` objects for a given `JobResult` instance.
|
|
1565
1565
|
"""
|
|
@@ -1567,7 +1567,7 @@ class JobLogEntryTableView(View):
|
|
|
1567
1567
|
queryset = JobResult.objects.all()
|
|
1568
1568
|
|
|
1569
1569
|
def get(self, request, pk=None):
|
|
1570
|
-
instance = self.queryset.
|
|
1570
|
+
instance = get_object_or_404(self.queryset.restrict(request.user, "view"), pk=pk)
|
|
1571
1571
|
filter_q = request.GET.get("q")
|
|
1572
1572
|
if filter_q:
|
|
1573
1573
|
queryset = instance.job_log_entries.filter(
|
|
@@ -1646,7 +1646,7 @@ class ObjectChangeView(generic.ObjectView):
|
|
|
1646
1646
|
}
|
|
1647
1647
|
|
|
1648
1648
|
|
|
1649
|
-
class ObjectChangeLogView(
|
|
1649
|
+
class ObjectChangeLogView(generic.GenericView):
|
|
1650
1650
|
"""
|
|
1651
1651
|
Present a history of changes made to a particular object.
|
|
1652
1652
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -1730,7 +1730,7 @@ class NoteDeleteView(generic.ObjectDeleteView):
|
|
|
1730
1730
|
queryset = Note.objects.all()
|
|
1731
1731
|
|
|
1732
1732
|
|
|
1733
|
-
class ObjectNotesView(
|
|
1733
|
+
class ObjectNotesView(generic.GenericView):
|
|
1734
1734
|
"""
|
|
1735
1735
|
Present a list of notes associated to a particular object.
|
|
1736
1736
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -1751,7 +1751,7 @@ class ObjectNotesView(View):
|
|
|
1751
1751
|
"assigned_object_id": obj.pk,
|
|
1752
1752
|
}
|
|
1753
1753
|
)
|
|
1754
|
-
notes_table = tables.NoteTable(obj.notes)
|
|
1754
|
+
notes_table = tables.NoteTable(obj.notes.restrict(request.user, "view"))
|
|
1755
1755
|
|
|
1756
1756
|
# Apply the request context
|
|
1757
1757
|
paginate = {
|
|
@@ -1972,7 +1972,7 @@ class SecretView(generic.ObjectView):
|
|
|
1972
1972
|
}
|
|
1973
1973
|
|
|
1974
1974
|
|
|
1975
|
-
class SecretProviderParametersFormView(
|
|
1975
|
+
class SecretProviderParametersFormView(generic.GenericView):
|
|
1976
1976
|
"""
|
|
1977
1977
|
Helper view to SecretView; retrieve the HTML form appropriate for entering parameters for a given SecretsProvider.
|
|
1978
1978
|
"""
|
nautobot/ipam/api/urls.py
CHANGED
|
@@ -2,8 +2,7 @@ from nautobot.core.api.routers import OrderedDefaultRouter
|
|
|
2
2
|
|
|
3
3
|
from . import views
|
|
4
4
|
|
|
5
|
-
router = OrderedDefaultRouter()
|
|
6
|
-
router.APIRootView = views.IPAMRootView
|
|
5
|
+
router = OrderedDefaultRouter(view_name="IPAM")
|
|
7
6
|
|
|
8
7
|
# Namespaces
|
|
9
8
|
router.register("namespaces", views.NamespaceViewSet)
|
nautobot/ipam/api/views.py
CHANGED
|
@@ -5,7 +5,6 @@ from drf_spectacular.utils import extend_schema
|
|
|
5
5
|
from rest_framework import status
|
|
6
6
|
from rest_framework.decorators import action
|
|
7
7
|
from rest_framework.response import Response
|
|
8
|
-
from rest_framework.routers import APIRootView
|
|
9
8
|
|
|
10
9
|
from nautobot.core.models.querysets import count_related
|
|
11
10
|
from nautobot.core.utils.config import get_settings_or_config
|
|
@@ -26,16 +25,6 @@ from nautobot.ipam.models import (
|
|
|
26
25
|
|
|
27
26
|
from . import serializers
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
class IPAMRootView(APIRootView):
|
|
31
|
-
"""
|
|
32
|
-
IPAM API root view
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def get_view_name(self):
|
|
36
|
-
return "IPAM"
|
|
37
|
-
|
|
38
|
-
|
|
39
28
|
#
|
|
40
29
|
# Namespace
|
|
41
30
|
#
|
|
@@ -118,7 +107,9 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
118
107
|
"""
|
|
119
108
|
prefix = get_object_or_404(self.queryset, pk=pk)
|
|
120
109
|
if request.method == "POST":
|
|
121
|
-
with cache.lock(
|
|
110
|
+
with cache.lock(
|
|
111
|
+
"nautobot.ipam.api.views.available_prefixes", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT
|
|
112
|
+
):
|
|
122
113
|
available_prefixes = prefix.get_available_prefixes()
|
|
123
114
|
|
|
124
115
|
# Validate Requested Prefixes' length
|
|
@@ -201,7 +192,9 @@ class PrefixViewSet(NautobotModelViewSet):
|
|
|
201
192
|
|
|
202
193
|
# Create the next available IP within the prefix
|
|
203
194
|
if request.method == "POST":
|
|
204
|
-
with cache.lock(
|
|
195
|
+
with cache.lock(
|
|
196
|
+
"nautobot.ipam.api.views.available_ips", blocking_timeout=5, timeout=settings.REDIS_LOCK_TIMEOUT
|
|
197
|
+
):
|
|
205
198
|
# Normalize to a list of objects
|
|
206
199
|
requested_ips = request.data if isinstance(request.data, list) else [request.data]
|
|
207
200
|
|
nautobot/ipam/tables.py
CHANGED
|
@@ -179,7 +179,6 @@ VLAN_LINK = """
|
|
|
179
179
|
{% url 'ipam:vlan_add' %}\
|
|
180
180
|
?vid={{ record.vid }}&vlan_group={{ vlan_group.pk }}\
|
|
181
181
|
{% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
|
|
182
|
-
{% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
|
|
183
182
|
" class="btn btn-xs btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>\
|
|
184
183
|
{% else %}
|
|
185
184
|
{{ record.available }} VLAN{{ record.available|pluralize }} available
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from django.test import override_settings
|
|
2
1
|
from django.urls import reverse
|
|
3
2
|
from rest_framework import status
|
|
4
3
|
|
|
@@ -9,8 +8,8 @@ class TestPrefix(APITestCase):
|
|
|
9
8
|
def setUp(self):
|
|
10
9
|
super().setUp()
|
|
11
10
|
self.api_url = reverse("graphql-api")
|
|
11
|
+
self.add_permissions("ipam.view_prefix")
|
|
12
12
|
|
|
13
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
14
13
|
def test_prefix_ip_version(self):
|
|
15
14
|
"""Test ip_version is available for a Prefix via GraphQL."""
|
|
16
15
|
get_prefixes_query = """
|
|
@@ -23,7 +22,7 @@ class TestPrefix(APITestCase):
|
|
|
23
22
|
}
|
|
24
23
|
"""
|
|
25
24
|
payload = {"query": get_prefixes_query}
|
|
26
|
-
response = self.client.post(self.api_url, payload, format="json")
|
|
25
|
+
response = self.client.post(self.api_url, payload, format="json", **self.header)
|
|
27
26
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
28
27
|
prefixes = response.data["data"]["prefixes"]
|
|
29
28
|
self.assertIsInstance(prefixes, list)
|
nautobot/ipam/views.py
CHANGED
|
@@ -775,7 +775,7 @@ class IPAddressEditView(generic.ObjectEditView):
|
|
|
775
775
|
_, error_msg = retrieve_interface_or_vminterface_from_request(request)
|
|
776
776
|
if error_msg:
|
|
777
777
|
messages.warning(request, error_msg)
|
|
778
|
-
return redirect(self.get_return_url(request
|
|
778
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
779
779
|
|
|
780
780
|
return super().dispatch(request, *args, **kwargs)
|
|
781
781
|
|
|
@@ -858,17 +858,17 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
|
|
|
858
858
|
"""
|
|
859
859
|
|
|
860
860
|
queryset = IPAddress.objects.all()
|
|
861
|
-
default_return_url = "ipam:ipaddress_add"
|
|
862
861
|
|
|
863
862
|
def dispatch(self, request, *args, **kwargs):
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
863
|
+
if request.user.is_authenticated:
|
|
864
|
+
# Redirect user if an interface has not been provided
|
|
865
|
+
if "interface" not in request.GET and "vminterface" not in request.GET:
|
|
866
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
867
867
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
868
|
+
_, error_msg = retrieve_interface_or_vminterface_from_request(request)
|
|
869
|
+
if error_msg:
|
|
870
|
+
messages.warning(request, error_msg)
|
|
871
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
872
872
|
|
|
873
873
|
return super().dispatch(request, *args, **kwargs)
|
|
874
874
|
|
|
@@ -973,7 +973,9 @@ class IPAddressMergeView(view_mixins.GetReturnURLMixin, view_mixins.ObjectPermis
|
|
|
973
973
|
# Check if there are at least two IP addresses for us to merge
|
|
974
974
|
# and if the skip button is pressed instead.
|
|
975
975
|
if "_skip" not in request.POST and not operation_invalid:
|
|
976
|
-
with cache.lock(
|
|
976
|
+
with cache.lock(
|
|
977
|
+
"nautobot.ipam.views.ipaddress_merge", blocking_timeout=15, timeout=settings.REDIS_LOCK_TIMEOUT
|
|
978
|
+
):
|
|
977
979
|
with transaction.atomic():
|
|
978
980
|
namespace = Namespace.objects.get(pk=merged_attributes.get("namespace"))
|
|
979
981
|
status = Status.objects.get(pk=merged_attributes.get("status"))
|
|
@@ -537,6 +537,20 @@
|
|
|
537
537
|
|
|
538
538
|
|
|
539
539
|
|
|
540
|
+
<li class="md-nav__item">
|
|
541
|
+
<a href="/projects/core/en/stable/user-guide/administration/installation/health-checks.html" class="md-nav__link">
|
|
542
|
+
Health Checks
|
|
543
|
+
</a>
|
|
544
|
+
</li>
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
540
554
|
<li class="md-nav__item">
|
|
541
555
|
<a href="/projects/core/en/stable/user-guide/administration/installation/selinux-troubleshooting.html" class="md-nav__link">
|
|
542
556
|
SELinux Troubleshooting
|
|
@@ -3871,8 +3885,8 @@
|
|
|
3871
3885
|
|
|
3872
3886
|
|
|
3873
3887
|
<li class="md-nav__item">
|
|
3874
|
-
<a href="/projects/core/en/stable/development/apps/api/ui-extensions/object-
|
|
3875
|
-
Object Detail
|
|
3888
|
+
<a href="/projects/core/en/stable/development/apps/api/ui-extensions/object-views.html" class="md-nav__link">
|
|
3889
|
+
Object Detail & List Views
|
|
3876
3890
|
</a>
|
|
3877
3891
|
</li>
|
|
3878
3892
|
|
|
@@ -4571,6 +4585,20 @@
|
|
|
4571
4585
|
|
|
4572
4586
|
|
|
4573
4587
|
|
|
4588
|
+
<li class="md-nav__item">
|
|
4589
|
+
<a href="/projects/core/en/stable/development/core/caching.html" class="md-nav__link">
|
|
4590
|
+
Caching
|
|
4591
|
+
</a>
|
|
4592
|
+
</li>
|
|
4593
|
+
|
|
4594
|
+
|
|
4595
|
+
|
|
4596
|
+
|
|
4597
|
+
|
|
4598
|
+
|
|
4599
|
+
|
|
4600
|
+
|
|
4601
|
+
|
|
4574
4602
|
<li class="md-nav__item">
|
|
4575
4603
|
<a href="/projects/core/en/stable/development/core/extending-models.html" class="md-nav__link">
|
|
4576
4604
|
Extending Models
|