nautobot 2.2.0b1__py3-none-any.whl → 2.2.1__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/__init__.py +31 -0
- 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/apps.py +1 -1
- nautobot/circuits/tests/test_filters.py +1 -1
- nautobot/core/api/routers.py +50 -3
- nautobot/core/api/utils.py +4 -0
- nautobot/core/api/views.py +21 -15
- nautobot/core/cli/__init__.py +18 -11
- nautobot/core/constants.py +85 -0
- nautobot/core/filters.py +7 -1
- nautobot/core/forms/widgets.py +1 -2
- nautobot/core/graphql/schema.py +1 -0
- nautobot/core/management/commands/generate_test_data.py +4 -4
- nautobot/core/models/__init__.py +1 -0
- nautobot/core/settings.py +24 -3
- nautobot/core/settings.yaml +20 -0
- nautobot/core/signals.py +1 -0
- nautobot/core/tables.py +2 -1
- nautobot/core/templates/admin/base.html +23 -94
- nautobot/core/templates/generic/object_retrieve.html +2 -2
- nautobot/core/templates/graphene/graphiql.html +18 -47
- nautobot/core/templates/inc/footer.html +5 -5
- nautobot/core/templates/inc/javascript.html +4 -4
- nautobot/core/templates/inc/media.html +2 -2
- nautobot/core/templates/inc/nav_menu.html +0 -7
- nautobot/core/templates/nautobot_config.py.j2 +14 -1
- nautobot/core/templates/rest_framework/api.html +12 -5
- nautobot/core/templatetags/helpers.py +2 -2
- nautobot/core/testing/__init__.py +1 -1
- nautobot/core/testing/filters.py +1 -1
- nautobot/core/testing/views.py +30 -0
- nautobot/core/tests/integration/test_view_authentication.py +68 -0
- nautobot/core/tests/test_api.py +13 -6
- nautobot/core/tests/test_csv.py +5 -4
- nautobot/core/tests/test_filters.py +2 -1
- nautobot/core/tests/test_graphql.py +4 -14
- nautobot/core/tests/test_navigations.py +3 -0
- nautobot/core/tests/test_views.py +44 -16
- nautobot/core/utils/data.py +1 -2
- nautobot/core/utils/lookup.py +126 -0
- nautobot/core/views/__init__.py +3 -7
- nautobot/core/views/generic.py +20 -6
- nautobot/core/views/mixins.py +7 -1
- nautobot/core/views/renderers.py +11 -6
- nautobot/dcim/api/serializers.py +4 -4
- nautobot/dcim/api/urls.py +2 -3
- nautobot/dcim/api/views.py +7 -18
- nautobot/dcim/apps.py +8 -4
- nautobot/dcim/elevations.py +5 -1
- nautobot/dcim/factory.py +7 -7
- nautobot/dcim/filters/__init__.py +16 -17
- nautobot/dcim/forms.py +61 -45
- nautobot/dcim/homepage.py +11 -3
- nautobot/dcim/management/commands/migrate_location_contacts.py +218 -0
- nautobot/dcim/migrations/0057_controller_models.py +11 -70
- nautobot/dcim/models/__init__.py +2 -2
- nautobot/dcim/models/devices.py +14 -16
- nautobot/dcim/models/racks.py +1 -3
- nautobot/dcim/navigation.py +23 -31
- nautobot/dcim/signals.py +6 -6
- nautobot/dcim/tables/__init__.py +2 -2
- nautobot/dcim/tables/devices.py +13 -16
- nautobot/dcim/tables/template_code.py +1 -1
- nautobot/dcim/templates/dcim/controller_create.html +70 -0
- nautobot/dcim/templates/dcim/controller_retrieve.html +35 -18
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +88 -0
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +75 -43
- nautobot/dcim/templates/dcim/device.html +11 -3
- nautobot/dcim/templates/dcim/device_edit.html +1 -1
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +4 -0
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +1 -1
- nautobot/dcim/tests/test_api.py +47 -6
- nautobot/dcim/tests/test_filters.py +92 -81
- nautobot/dcim/tests/test_graphql.py +11 -1
- nautobot/dcim/tests/test_models.py +15 -15
- nautobot/dcim/tests/test_signals.py +3 -1
- nautobot/dcim/tests/test_views.py +24 -12
- nautobot/dcim/urls.py +1 -1
- nautobot/dcim/views.py +25 -15
- nautobot/extras/api/serializers.py +20 -1
- nautobot/extras/api/urls.py +1 -2
- nautobot/extras/api/views.py +0 -10
- nautobot/extras/apps.py +7 -0
- nautobot/extras/context_managers.py +15 -4
- nautobot/extras/filters/__init__.py +53 -2
- nautobot/extras/filters/customfields.py +14 -9
- nautobot/extras/filters/mixins.py +6 -1
- nautobot/extras/forms/contacts.py +7 -0
- nautobot/extras/health_checks.py +1 -0
- nautobot/extras/jobs.py +1 -0
- nautobot/extras/managers.py +15 -2
- nautobot/extras/models/contacts.py +1 -0
- nautobot/extras/models/customfields.py +25 -2
- nautobot/extras/models/datasources.py +1 -0
- nautobot/extras/models/mixins.py +1 -0
- nautobot/extras/navigation.py +71 -65
- nautobot/extras/plugins/__init__.py +2 -1
- nautobot/extras/plugins/views.py +7 -11
- nautobot/extras/querysets.py +1 -2
- nautobot/extras/secrets/providers.py +1 -0
- nautobot/extras/signals.py +15 -5
- nautobot/extras/tasks.py +70 -17
- nautobot/extras/tests/test_api.py +2 -4
- nautobot/extras/tests/test_customfields.py +72 -9
- nautobot/extras/tests/test_dynamicgroups.py +2 -0
- nautobot/extras/tests/test_filters.py +89 -4
- nautobot/extras/tests/test_models.py +9 -0
- nautobot/extras/tests/test_relationships.py +10 -1
- nautobot/extras/tests/test_views.py +112 -1
- nautobot/extras/views.py +18 -17
- nautobot/ipam/api/serializers.py +10 -0
- nautobot/ipam/api/urls.py +1 -2
- nautobot/ipam/api/views.py +0 -11
- nautobot/ipam/apps.py +3 -2
- nautobot/ipam/tables.py +2 -22
- nautobot/ipam/tests/test_graphql.py +2 -3
- nautobot/ipam/tests/test_tables.py +42 -0
- nautobot/ipam/tests/test_views.py +1 -0
- nautobot/ipam/views.py +9 -9
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +126 -73
- nautobot/project-static/docs/apps/index.html +127 -71
- nautobot/project-static/docs/apps/nautobot-apps.html +127 -71
- nautobot/project-static/docs/assets/javascripts/{bundle.8fd75fb4.min.js → bundle.bd41221c.min.js} +2 -2
- nautobot/project-static/docs/assets/javascripts/{bundle.8fd75fb4.min.js.map → bundle.bd41221c.min.js.map} +3 -3
- nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +167 -73
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +165 -72
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +128 -72
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +127 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +345 -71
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +172 -73
- nautobot/project-static/docs/development/apps/api/configuration-view.html +127 -71
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +127 -71
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +127 -71
- nautobot/project-static/docs/development/apps/api/models/global-search.html +127 -71
- nautobot/project-static/docs/development/apps/api/models/graphql.html +127 -71
- nautobot/project-static/docs/development/apps/api/models/index.html +127 -71
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +127 -71
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +127 -71
- nautobot/project-static/docs/development/apps/api/prometheus.html +127 -71
- nautobot/project-static/docs/development/apps/api/setup.html +127 -71
- nautobot/project-static/docs/development/apps/api/testing.html +127 -71
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +127 -71
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +127 -71
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +127 -71
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +127 -71
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/base-template.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +141 -80
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +144 -83
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/index.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/notes.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +127 -71
- nautobot/project-static/docs/development/apps/api/views/urls.html +127 -71
- nautobot/project-static/docs/development/apps/index.html +127 -71
- nautobot/project-static/docs/development/apps/migration/code-updates.html +127 -71
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +127 -71
- nautobot/project-static/docs/development/apps/migration/from-v1.html +127 -71
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +127 -71
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +127 -71
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +127 -71
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +127 -71
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +127 -71
- nautobot/project-static/docs/development/core/application-registry.html +127 -71
- nautobot/project-static/docs/development/core/best-practices.html +145 -79
- nautobot/project-static/docs/development/core/bootstrap-ui.html +127 -71
- nautobot/project-static/docs/development/core/caching.html +127 -71
- nautobot/project-static/docs/development/core/controllers.html +141 -275
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +127 -71
- nautobot/project-static/docs/development/core/extending-models.html +13 -8166
- nautobot/project-static/docs/development/core/generic-views.html +142 -86
- nautobot/project-static/docs/development/core/getting-started.html +146 -81
- nautobot/project-static/docs/development/core/homepage.html +145 -89
- nautobot/project-static/docs/development/core/index.html +127 -71
- nautobot/project-static/docs/development/core/model-checklist.html +8354 -0
- nautobot/project-static/docs/development/core/model-features.html +130 -74
- nautobot/project-static/docs/development/core/natural-keys.html +127 -71
- nautobot/project-static/docs/development/core/navigation-menu.html +127 -71
- nautobot/project-static/docs/development/core/release-checklist.html +127 -71
- nautobot/project-static/docs/development/core/role-internals.html +127 -71
- nautobot/project-static/docs/development/core/settings.html +127 -71
- nautobot/project-static/docs/development/core/style-guide.html +127 -71
- nautobot/project-static/docs/development/core/templates.html +127 -71
- nautobot/project-static/docs/development/core/testing.html +127 -71
- nautobot/project-static/docs/development/core/user-preferences.html +127 -71
- nautobot/project-static/docs/development/extending-models.html +3 -3
- nautobot/project-static/docs/development/index.html +127 -71
- nautobot/project-static/docs/development/jobs/index.html +128 -72
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +127 -71
- nautobot/project-static/docs/index.html +126 -73
- nautobot/project-static/docs/models/dcim/{controllerdevicegroup.html → controllermanageddevicegroup.html} +3 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/release-notes/index.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.0.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.1.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.2.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.3.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.4.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.5.html +127 -71
- nautobot/project-static/docs/release-notes/version-1.6.html +127 -71
- nautobot/project-static/docs/release-notes/version-2.0.html +127 -71
- nautobot/project-static/docs/release-notes/version-2.1.html +538 -254
- nautobot/project-static/docs/release-notes/version-2.2.html +520 -101
- nautobot/project-static/docs/requirements.txt +3 -3
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +264 -259
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +127 -71
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +127 -71
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +127 -71
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +127 -71
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +192 -71
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +127 -71
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -71
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +127 -71
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +130 -74
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/docker.html +134 -74
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +8616 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/index.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +127 -71
- nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +130 -74
- nautobot/project-static/docs/user-guide/administration/installation/services.html +127 -71
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +127 -71
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +127 -71
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +127 -71
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +127 -71
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +362 -79
- nautobot/project-static/docs/user-guide/core-data-model/dcim/{controllerdevicegroup.html → controllermanageddevicegroup.html} +210 -85
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +130 -74
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +138 -71
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +138 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +127 -71
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/{contact-and-team.html → contacts-and-teams.html} +128 -72
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +129 -73
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +129 -73
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +127 -71
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +127 -71
- nautobot/project-static/docs/user-guide/index.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +127 -71
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +127 -71
- nautobot/project-static/jquery/jquery-3.7.1.min.js +2 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_444444_256x240.png +0 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_555555_256x240.png +0 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_777620_256x240.png +0 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_777777_256x240.png +0 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_cc0000_256x240.png +0 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_ffffff_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +7 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +6 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +5 -0
- nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/jquery-ui.theme.min.css +1 -1
- nautobot/tenancy/api/urls.py +1 -2
- nautobot/tenancy/api/views.py +0 -12
- nautobot/tenancy/tables.py +1 -1
- nautobot/tenancy/tests/test_views.py +1 -0
- 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-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/METADATA +24 -24
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/RECORD +418 -412
- nautobot/dcim/templates/dcim/controllerdevicegroup_create.html +0 -43
- nautobot/project-static/docs/assets/stylesheets/main.f2e4d321.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.f2e4d321.min.css.map +0 -1
- nautobot/project-static/jquery/jquery-3.6.0.min.js +0 -2
- nautobot/project-static/jquery-ui-1.13.1/jquery-ui.min.css +0 -7
- nautobot/project-static/jquery-ui-1.13.1/jquery-ui.min.js +0 -6
- nautobot/project-static/jquery-ui-1.13.1/jquery-ui.structure.min.css +0 -5
- /nautobot/dcim/templates/dcim/{controllerdevicegroup_retrieve.html → controllermanageddevicegroup_retrieve.html} +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/NOTICE +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/WHEEL +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.1.dist-info}/entry_points.txt +0 -0
nautobot/core/tests/test_api.py
CHANGED
|
@@ -770,6 +770,7 @@ class APIOrderingTestCase(testing.APITestCase):
|
|
|
770
770
|
"TextField": "admin_contact",
|
|
771
771
|
"DateTimeField": "created",
|
|
772
772
|
}
|
|
773
|
+
cls.maxDiff = None
|
|
773
774
|
|
|
774
775
|
def _validate_sorted_response(self, response, queryset, field_name, is_fk_field=False):
|
|
775
776
|
self.assertHttpStatus(response, 200)
|
|
@@ -794,18 +795,24 @@ class APIOrderingTestCase(testing.APITestCase):
|
|
|
794
795
|
"""Tests that results are returned in the expected ascending order."""
|
|
795
796
|
|
|
796
797
|
for field_type, field_name in self.field_type_map.items():
|
|
797
|
-
with self.subTest(f"Testing {field_type}"):
|
|
798
|
-
|
|
799
|
-
|
|
798
|
+
with self.subTest(f"Testing {field_type} {field_name}"):
|
|
799
|
+
# Use `name` as a secondary sort as fields like `asn` and `admin_contact` may be null
|
|
800
|
+
response = self.client.get(f"{self.url}?sort={field_name},name&limit=10", **self.header)
|
|
801
|
+
self._validate_sorted_response(
|
|
802
|
+
response, Provider.objects.all().order_by(field_name, "name"), field_name
|
|
803
|
+
)
|
|
800
804
|
|
|
801
805
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
802
806
|
def test_descending_sort(self):
|
|
803
807
|
"""Tests that results are returned in the expected descending order."""
|
|
804
808
|
|
|
805
809
|
for field_type, field_name in self.field_type_map.items():
|
|
806
|
-
with self.subTest(f"Testing {field_type}"):
|
|
807
|
-
|
|
808
|
-
|
|
810
|
+
with self.subTest(f"Testing {field_type} {field_name}"):
|
|
811
|
+
# Use `name` as a secondary sort as fields like `asn` and `admin_contact` may be null
|
|
812
|
+
response = self.client.get(f"{self.url}?sort=-{field_name},name&limit=10", **self.header)
|
|
813
|
+
self._validate_sorted_response(
|
|
814
|
+
response, Provider.objects.all().order_by(f"-{field_name}", "name"), field_name
|
|
815
|
+
)
|
|
809
816
|
|
|
810
817
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
811
818
|
def test_sorting_tree_node_models(self):
|
nautobot/core/tests/test_csv.py
CHANGED
|
@@ -4,7 +4,7 @@ from django.urls import reverse
|
|
|
4
4
|
|
|
5
5
|
from nautobot.core.constants import CSV_NO_OBJECT, CSV_NULL_TYPE, VARBINARY_IP_FIELD_REPR_OF_CSV_NO_OBJECT
|
|
6
6
|
from nautobot.dcim.api.serializers import DeviceSerializer
|
|
7
|
-
from nautobot.dcim.models.devices import Device, DeviceType
|
|
7
|
+
from nautobot.dcim.models.devices import Controller, Device, DeviceType
|
|
8
8
|
from nautobot.dcim.models.locations import Location
|
|
9
9
|
from nautobot.extras.models.roles import Role
|
|
10
10
|
from nautobot.extras.models.statuses import Status
|
|
@@ -25,6 +25,7 @@ class CSVParsingRelatedTestCase(TestCase):
|
|
|
25
25
|
devicerole = Role.objects.get_for_model(Device).first()
|
|
26
26
|
device_status = Status.objects.get_for_model(Device).first()
|
|
27
27
|
tags = Tag.objects.get_for_model(Device).all()[:3]
|
|
28
|
+
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
28
29
|
Device.objects.all().delete()
|
|
29
30
|
self.device = Device.objects.create(
|
|
30
31
|
device_type=devicetype,
|
|
@@ -92,7 +93,7 @@ class CSVParsingRelatedTestCase(TestCase):
|
|
|
92
93
|
"primary_ip6__host",
|
|
93
94
|
"cluster__name",
|
|
94
95
|
"virtual_chassis__name",
|
|
95
|
-
"
|
|
96
|
+
"controller_managed_device_group__name",
|
|
96
97
|
"device_redundancy_group__name",
|
|
97
98
|
"software_version__platform__name",
|
|
98
99
|
"software_version__version",
|
|
@@ -119,7 +120,7 @@ class CSVParsingRelatedTestCase(TestCase):
|
|
|
119
120
|
"primary_ip6",
|
|
120
121
|
"cluster",
|
|
121
122
|
"virtual_chassis",
|
|
122
|
-
"
|
|
123
|
+
"controller_managed_device_group",
|
|
123
124
|
"device_redundancy_group",
|
|
124
125
|
"secrets_group",
|
|
125
126
|
]
|
|
@@ -224,7 +225,7 @@ class CSVParsingRelatedTestCase(TestCase):
|
|
|
224
225
|
"primary_ip6__host": CSV_NO_OBJECT,
|
|
225
226
|
"cluster__name": CSV_NO_OBJECT,
|
|
226
227
|
"virtual_chassis__name": CSV_NO_OBJECT,
|
|
227
|
-
"
|
|
228
|
+
"controller_managed_device_group__name": CSV_NO_OBJECT,
|
|
228
229
|
"device_redundancy_group__name": CSV_NO_OBJECT,
|
|
229
230
|
"software_version__platform__name": CSV_NO_OBJECT,
|
|
230
231
|
"software_version__version": CSV_NO_OBJECT,
|
|
@@ -17,7 +17,7 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
|
17
17
|
from nautobot.core.models import fields as core_fields
|
|
18
18
|
from nautobot.core.utils import lookup
|
|
19
19
|
from nautobot.dcim import choices as dcim_choices, filters as dcim_filters, models as dcim_models
|
|
20
|
-
from nautobot.dcim.models import Device
|
|
20
|
+
from nautobot.dcim.models import Controller, Device
|
|
21
21
|
from nautobot.extras import models as extras_models
|
|
22
22
|
from nautobot.extras.utils import FeatureQuery
|
|
23
23
|
from nautobot.ipam import models as ipam_models
|
|
@@ -830,6 +830,7 @@ class DynamicFilterLookupExpressionTest(TestCase):
|
|
|
830
830
|
@classmethod
|
|
831
831
|
def setUpTestData(cls):
|
|
832
832
|
manufacturers = dcim_models.Manufacturer.objects.all()[:3]
|
|
833
|
+
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
833
834
|
Device.objects.all().delete()
|
|
834
835
|
|
|
835
836
|
device_types = (
|
|
@@ -43,6 +43,7 @@ from nautobot.dcim.models import (
|
|
|
43
43
|
Cable,
|
|
44
44
|
ConsolePort,
|
|
45
45
|
ConsoleServerPort,
|
|
46
|
+
Controller,
|
|
46
47
|
Device,
|
|
47
48
|
DeviceType,
|
|
48
49
|
FrontPort,
|
|
@@ -630,21 +631,9 @@ class GraphQLAPIPermissionTest(GraphQLTestCaseBase):
|
|
|
630
631
|
self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
|
|
631
632
|
|
|
632
633
|
def test_graphql_api_no_token(self):
|
|
633
|
-
"""Validate unauthenticated users are not able to query anything
|
|
634
|
+
"""Validate unauthenticated users are not able to query anything."""
|
|
634
635
|
response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
|
|
635
|
-
self.assertEqual(response.status_code, status.
|
|
636
|
-
self.assertIsInstance(response.data["data"]["racks"], list)
|
|
637
|
-
names = [item["name"] for item in response.data["data"]["racks"]]
|
|
638
|
-
self.assertEqual(names, [])
|
|
639
|
-
|
|
640
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
641
|
-
def test_graphql_api_no_token_exempt(self):
|
|
642
|
-
"""Validate unauthenticated users are able to query based on the exempt permissions."""
|
|
643
|
-
response = self.client.post(self.api_url, {"query": self.get_racks_query}, format="json")
|
|
644
|
-
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
645
|
-
self.assertIsInstance(response.data["data"]["racks"], list)
|
|
646
|
-
names = [item["name"] for item in response.data["data"]["racks"]]
|
|
647
|
-
self.assertEqual(names, ["Rack 1-1", "Rack 1-2", "Rack 2-1", "Rack 2-2"])
|
|
636
|
+
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
|
648
637
|
|
|
649
638
|
def test_graphql_api_wrong_token(self):
|
|
650
639
|
"""Validate a wrong token return 403."""
|
|
@@ -721,6 +710,7 @@ class GraphQLQueryTest(GraphQLTestCaseBase):
|
|
|
721
710
|
|
|
722
711
|
# Remove random IPAddress and Device fixtures for this custom test
|
|
723
712
|
IPAddress.objects.all().delete()
|
|
713
|
+
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
724
714
|
Device.objects.all().delete()
|
|
725
715
|
|
|
726
716
|
# Initialize fake request that will be required to execute GraphQL query
|
|
@@ -44,6 +44,8 @@ class NavMenuTestCase(TestCase):
|
|
|
44
44
|
expected_name = "Interfaces"
|
|
45
45
|
elif expected_name == "Object Changes":
|
|
46
46
|
expected_name = "Change Log"
|
|
47
|
+
elif expected_name == "Controller Managed Device Groups":
|
|
48
|
+
expected_name = "Managed Device Groups"
|
|
47
49
|
self.assertEqual(item_details["name"], expected_name)
|
|
48
50
|
if item_url == get_route_for_model(view_model, "list"):
|
|
49
51
|
# Not assertEqual as some menu items have additional permissions defined.
|
|
@@ -87,6 +89,7 @@ class NavMenuTestCase(TestCase):
|
|
|
87
89
|
self.assertEqual(expected_perms[tab_name], tab_details["permissions"])
|
|
88
90
|
|
|
89
91
|
|
|
92
|
+
@tag("unit")
|
|
90
93
|
class NewUINavTest(TestCase):
|
|
91
94
|
@patch.dict(registry, values={"new_ui_nav_menu": {}}, clear=True)
|
|
92
95
|
def test_build_new_ui_nav_menu(self):
|
|
@@ -2,6 +2,7 @@ import re
|
|
|
2
2
|
from unittest import mock
|
|
3
3
|
import urllib.parse
|
|
4
4
|
|
|
5
|
+
from django.apps import apps
|
|
5
6
|
from django.contrib.contenttypes.models import ContentType
|
|
6
7
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
7
8
|
from django.test import override_settings, RequestFactory
|
|
@@ -9,6 +10,7 @@ from django.test.utils import override_script_prefix
|
|
|
9
10
|
from django.urls import get_script_prefix, reverse
|
|
10
11
|
from prometheus_client.parser import text_string_to_metric_families
|
|
11
12
|
|
|
13
|
+
from nautobot.core.constants import GLOBAL_SEARCH_EXCLUDE_LIST
|
|
12
14
|
from nautobot.core.testing import TestCase
|
|
13
15
|
from nautobot.core.testing.api import APITestCase
|
|
14
16
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
@@ -71,6 +73,26 @@ class HomeViewTestCase(TestCase):
|
|
|
71
73
|
response = self.client.get(f"{url}?{urllib.parse.urlencode(params)}")
|
|
72
74
|
self.assertHttpStatus(response, 200)
|
|
73
75
|
|
|
76
|
+
def test_appropriate_models_included_in_global_search(self):
|
|
77
|
+
# Gather core app configs
|
|
78
|
+
existing_models = []
|
|
79
|
+
global_searchable_models = []
|
|
80
|
+
for app_name in ["circuits", "dcim", "extras", "ipam", "tenancy", "virtualization"]:
|
|
81
|
+
app_config = apps.get_app_config(app_name)
|
|
82
|
+
existing_models += [model._meta.model_name for model in app_config.get_models()]
|
|
83
|
+
global_searchable_models += app_config.searchable_models
|
|
84
|
+
|
|
85
|
+
# Remove those models that are not searchable
|
|
86
|
+
existing_models = [model for model in existing_models if model not in GLOBAL_SEARCH_EXCLUDE_LIST]
|
|
87
|
+
existing_models.sort()
|
|
88
|
+
|
|
89
|
+
# See if there are any models that are missing from global search
|
|
90
|
+
difference = [model for model in existing_models if model not in global_searchable_models]
|
|
91
|
+
if difference:
|
|
92
|
+
self.fail(
|
|
93
|
+
f'Existing model/models {",".join(difference)} are not included in the searchable_models attribute of the app config.\nIf you do not want the models to be searchable, please include them in the GLOBAL_SEARCH_EXCLUDE_LIST constant in nautobot.core.constants.'
|
|
94
|
+
)
|
|
95
|
+
|
|
74
96
|
def make_request(self):
|
|
75
97
|
url = reverse("home")
|
|
76
98
|
response = self.client.get(url)
|
|
@@ -305,25 +327,31 @@ class LoginUITestCase(TestCase):
|
|
|
305
327
|
sso_login_search_result = self.make_request()
|
|
306
328
|
self.assertIsNotNone(sso_login_search_result)
|
|
307
329
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
"""Assert that api docs and graphql redirects to login page if user is unauthenticated."""
|
|
330
|
+
def test_graphql_redirects_back_to_login_unauthenticated(self):
|
|
331
|
+
"""Assert that graphql redirects to login page if user is unauthenticated."""
|
|
311
332
|
self.client.logout()
|
|
312
333
|
headers = {"HTTP_ACCEPT": "text/html"}
|
|
313
|
-
|
|
334
|
+
url = reverse("graphql")
|
|
335
|
+
response = self.client.get(url, follow=True, **headers)
|
|
336
|
+
self.assertHttpStatus(response, 200)
|
|
337
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
338
|
+
response_content = response.content.decode(response.charset).replace("\n", "")
|
|
339
|
+
for footer_text in self.footer_elements:
|
|
340
|
+
self.assertNotIn(footer_text, response_content)
|
|
341
|
+
|
|
342
|
+
def test_api_docs_403_unauthenticated(self):
|
|
343
|
+
"""Assert that api docs return a 403 Forbidden if user is unauthenticated."""
|
|
344
|
+
self.client.logout()
|
|
345
|
+
urls = [
|
|
346
|
+
reverse("api_docs"),
|
|
347
|
+
reverse("api_redocs"),
|
|
348
|
+
reverse("schema"),
|
|
349
|
+
reverse("schema_json"),
|
|
350
|
+
reverse("schema_yaml"),
|
|
351
|
+
]
|
|
314
352
|
for url in urls:
|
|
315
|
-
response = self.client.get(url
|
|
316
|
-
self.assertHttpStatus(response,
|
|
317
|
-
redirect_chain = [(f"/login/?next={url}", 302)]
|
|
318
|
-
self.assertEqual(response.redirect_chain, redirect_chain)
|
|
319
|
-
response_content = response.content.decode(response.charset).replace("\n", "")
|
|
320
|
-
# Assert Footer items(`self.footer_elements`), Banner and Banner Top is hidden
|
|
321
|
-
for footer_text in self.footer_elements:
|
|
322
|
-
self.assertNotIn(footer_text, response_content)
|
|
323
|
-
# Only API Docs implements BANNERS
|
|
324
|
-
if url == urls[0]:
|
|
325
|
-
self.assertNotIn("Hello, Banner Top", response_content)
|
|
326
|
-
self.assertNotIn("Hello, Banner Bottom", response_content)
|
|
353
|
+
response = self.client.get(url)
|
|
354
|
+
self.assertHttpStatus(response, 403)
|
|
327
355
|
|
|
328
356
|
|
|
329
357
|
class MetricsViewTestCase(TestCase):
|
nautobot/core/utils/data.py
CHANGED
nautobot/core/utils/lookup.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""Utilities for looking up related classes and information."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import re
|
|
4
5
|
|
|
5
6
|
from django.apps import apps
|
|
6
7
|
from django.conf import settings
|
|
7
8
|
from django.contrib.auth.models import Group
|
|
8
9
|
from django.contrib.contenttypes.models import ContentType
|
|
9
10
|
from django.db.models import Model
|
|
11
|
+
from django.urls import get_resolver, URLPattern, URLResolver
|
|
10
12
|
from django.utils.module_loading import import_string
|
|
11
13
|
|
|
12
14
|
|
|
@@ -232,3 +234,127 @@ def get_created_and_last_updated_usernames_for_model(instance):
|
|
|
232
234
|
last_updated_by = last_updated_by_record.user_name
|
|
233
235
|
|
|
234
236
|
return created_by, last_updated_by
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_url_patterns(urlconf=None, patterns_list=None, base_path="/"):
|
|
240
|
+
"""
|
|
241
|
+
Recursively yield a list of registered URL patterns.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
urlconf (URLConf): Python module such as `nautobot.core.urls`.
|
|
245
|
+
Default if unspecified is the value of `settings.ROOT_URLCONF`, i.e. the `nautobot.core.urls` module.
|
|
246
|
+
patterns_list (list): Used in recursion. Generally can be omitted on initial call.
|
|
247
|
+
Default if unspecified is the `url_patterns` attribute of the given `urlconf` module.
|
|
248
|
+
base_path (str): String to prepend to all URL patterns yielded.
|
|
249
|
+
Default if unspecified is the string `"/"`.
|
|
250
|
+
|
|
251
|
+
Yields:
|
|
252
|
+
(str): Each URL pattern defined in the given urlconf and its descendants
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
>>> generator = get_url_patterns()
|
|
256
|
+
>>> next(generator)
|
|
257
|
+
'/'
|
|
258
|
+
>>> next(generator)
|
|
259
|
+
'/search/'
|
|
260
|
+
>>> next(generator)
|
|
261
|
+
'/login/'
|
|
262
|
+
>>> next(generator)
|
|
263
|
+
'/logout/'
|
|
264
|
+
>>> next(generator)
|
|
265
|
+
'/circuits/circuits/<uuid:pk>/terminations/swap/'
|
|
266
|
+
|
|
267
|
+
>>> import example_plugin.urls as example_urls
|
|
268
|
+
>>> for url_pattern in get_url_patterns(example_urls, base_path="/plugins/example-app/"):
|
|
269
|
+
... print(url_pattern)
|
|
270
|
+
...
|
|
271
|
+
/plugins/example-app/
|
|
272
|
+
/plugins/example-app/config/
|
|
273
|
+
/plugins/example-app/models/<uuid:pk>/dynamic-groups/
|
|
274
|
+
/plugins/example-app/other-models/<uuid:pk>/dynamic-groups/
|
|
275
|
+
/plugins/example-app/docs/
|
|
276
|
+
/plugins/example-app/circuits/<uuid:pk>/example-app-tab/
|
|
277
|
+
/plugins/example-app/devices/<uuid:pk>/example-app-tab-1/
|
|
278
|
+
/plugins/example-app/devices/<uuid:pk>/example-app-tab-2/
|
|
279
|
+
/plugins/example-app/override-target/
|
|
280
|
+
/plugins/example-app/^models/$
|
|
281
|
+
/plugins/example-app/^models/add/$
|
|
282
|
+
/plugins/example-app/^models/import/$
|
|
283
|
+
/plugins/example-app/^models/edit/$
|
|
284
|
+
/plugins/example-app/^models/delete/$
|
|
285
|
+
/plugins/example-app/^models/all-names/$
|
|
286
|
+
/plugins/example-app/^models/(?P<pk>[^/.]+)/$
|
|
287
|
+
/plugins/example-app/^models/(?P<pk>[^/.]+)/delete/$
|
|
288
|
+
/plugins/example-app/^models/(?P<pk>[^/.]+)/edit/$
|
|
289
|
+
/plugins/example-app/^models/(?P<pk>[^/.]+)/changelog/$
|
|
290
|
+
/plugins/example-app/^models/(?P<pk>[^/.]+)/notes/$
|
|
291
|
+
/plugins/example-app/^other-models/$
|
|
292
|
+
/plugins/example-app/^other-models/add/$
|
|
293
|
+
/plugins/example-app/^other-models/edit/$
|
|
294
|
+
/plugins/example-app/^other-models/delete/$
|
|
295
|
+
/plugins/example-app/^other-models/(?P<pk>[^/.]+)/$
|
|
296
|
+
/plugins/example-app/^other-models/(?P<pk>[^/.]+)/delete/$
|
|
297
|
+
/plugins/example-app/^other-models/(?P<pk>[^/.]+)/edit/$
|
|
298
|
+
/plugins/example-app/^other-models/(?P<pk>[^/.]+)/changelog/$
|
|
299
|
+
/plugins/example-app/^other-models/(?P<pk>[^/.]+)/notes/$
|
|
300
|
+
"""
|
|
301
|
+
if urlconf is None:
|
|
302
|
+
urlconf = settings.ROOT_URLCONF
|
|
303
|
+
if patterns_list is None:
|
|
304
|
+
patterns_list = get_resolver(urlconf).url_patterns
|
|
305
|
+
|
|
306
|
+
for item in patterns_list:
|
|
307
|
+
if isinstance(item, URLPattern):
|
|
308
|
+
yield base_path + str(item.pattern)
|
|
309
|
+
elif isinstance(item, URLResolver):
|
|
310
|
+
# Recurse!
|
|
311
|
+
yield from get_url_patterns(urlconf, item.url_patterns, base_path + str(item.pattern))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def get_url_for_url_pattern(url_pattern):
|
|
315
|
+
"""
|
|
316
|
+
Given a URL pattern, construct a URL string that would match that pattern.
|
|
317
|
+
|
|
318
|
+
Examples:
|
|
319
|
+
>>> get_url_for_url_pattern("/plugins/example-app/^models/(?P<pk>[^/.]+)/$")
|
|
320
|
+
'/plugins/example-app/models/00000000-0000-0000-0000-000000000000/'
|
|
321
|
+
>>> get_url_for_url_pattern("/circuits/circuit-terminations/<uuid:termination_a_id>/connect/<str:termination_b_type>/")
|
|
322
|
+
'/circuits/circuit-terminations/00000000-0000-0000-0000-000000000000/connect/string/'
|
|
323
|
+
"""
|
|
324
|
+
url = url_pattern
|
|
325
|
+
# Fixup tokens in path-style "classic" view URLs:
|
|
326
|
+
# "/admin/users/user/<id>/password/"
|
|
327
|
+
url = re.sub(r"<id>", "00000000-0000-0000-0000-000000000000", url)
|
|
328
|
+
# "/silk/request/<uuid:request_id>/profile/<int:profile_id>/"
|
|
329
|
+
url = re.sub(r"<int:\w+>", "1", url)
|
|
330
|
+
# "/admin/admin/logentry/<path:object_id>/"
|
|
331
|
+
url = re.sub(r"<path:\w+>", "1", url)
|
|
332
|
+
# "/dcim/sites/<slug:slug>/"
|
|
333
|
+
url = re.sub(r"<slug:\w+>", "slug", url)
|
|
334
|
+
# "/apps/installed-apps/<str:app>/"
|
|
335
|
+
url = re.sub(r"<str:\w+>", "string", url)
|
|
336
|
+
# "/dcim/locations/<uuid:pk>/"
|
|
337
|
+
url = re.sub(r"<uuid:\w+>", "00000000-0000-0000-0000-000000000000", url)
|
|
338
|
+
# "/api/circuits/<drf_format_suffix:format>"
|
|
339
|
+
url = re.sub(r"<drf_format_suffix:\w+>", ".json", url)
|
|
340
|
+
# tokens in regexp-style router urls, including REST and NautobotUIViewSet:
|
|
341
|
+
# "/extras/^external-integrations/(?P<pk>[^/.]+)/$"
|
|
342
|
+
# "/api/virtualization/^interfaces/(?P<pk>[^/.]+)/$"
|
|
343
|
+
# "/api/virtualization/^interfaces/(?P<pk>[^/.]+)\\.(?P<format>[a-z0-9]+)/?$"
|
|
344
|
+
url = re.sub(r"[$^]", "", url)
|
|
345
|
+
url = re.sub(r"/\?", "/", url)
|
|
346
|
+
url = re.sub(r"\(\?P<app_label>[^)]+\)", "users", url)
|
|
347
|
+
url = re.sub(r"\(\?P<class_path>[^)]+\)", "foo/bar/baz", url)
|
|
348
|
+
url = re.sub(r"\(\?P<format>[^)]+\)", "json", url)
|
|
349
|
+
url = re.sub(r"\(\?P<name>[^)]+\)", "string", url)
|
|
350
|
+
url = re.sub(r"\(\?P<pk>[^)]+\)", "00000000-0000-0000-0000-000000000000", url)
|
|
351
|
+
url = re.sub(r"\(\?P<slug>[^)]+\)", "string", url)
|
|
352
|
+
url = re.sub(r"\(\?P<url>[^)]+\)", "any", url)
|
|
353
|
+
# Fallthru for generic URL parameters
|
|
354
|
+
url = re.sub(r"\(\?P<\w+>[^)]+\)\??", "unknown", url)
|
|
355
|
+
url = re.sub(r"\\", "", url)
|
|
356
|
+
|
|
357
|
+
if any(char in url for char in "<>[]()?+^$"):
|
|
358
|
+
raise RuntimeError(f"Unhandled token in URL {url} derived from {url_pattern}")
|
|
359
|
+
|
|
360
|
+
return url
|
nautobot/core/views/__init__.py
CHANGED
|
@@ -11,7 +11,7 @@ from django.contrib.auth.decorators import permission_required
|
|
|
11
11
|
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin
|
|
12
12
|
from django.contrib.contenttypes.models import ContentType
|
|
13
13
|
from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
14
|
-
from django.shortcuts import get_object_or_404,
|
|
14
|
+
from django.shortcuts import get_object_or_404, render
|
|
15
15
|
from django.template import loader, RequestContext, Template
|
|
16
16
|
from django.template.exceptions import TemplateDoesNotExist
|
|
17
17
|
from django.urls import resolve, reverse
|
|
@@ -210,7 +210,7 @@ class SearchView(AccessMixin, View):
|
|
|
210
210
|
)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
-
class StaticMediaFailureView(View):
|
|
213
|
+
class StaticMediaFailureView(View): # NOT using LoginRequiredMixin here as this may happen even on the login page
|
|
214
214
|
"""
|
|
215
215
|
Display a user-friendly error message with troubleshooting tips when a static media file fails to load.
|
|
216
216
|
"""
|
|
@@ -265,12 +265,8 @@ def csrf_failure(request, reason="", template_name="403_csrf_failure.html"):
|
|
|
265
265
|
return HttpResponseForbidden(t.render(context), content_type="text/html")
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
class CustomGraphQLView(GraphQLView):
|
|
268
|
+
class CustomGraphQLView(LoginRequiredMixin, GraphQLView):
|
|
269
269
|
def render_graphiql(self, request, **data):
|
|
270
|
-
if not request.user.is_authenticated:
|
|
271
|
-
graphql_url = reverse("graphql")
|
|
272
|
-
login_url = reverse(settings.LOGIN_URL)
|
|
273
|
-
return redirect(f"{login_url}?next={graphql_url}")
|
|
274
270
|
query_name = request.GET.get("name")
|
|
275
271
|
if query_name:
|
|
276
272
|
data["obj"] = GraphQLQuery.objects.get(name=query_name)
|
nautobot/core/views/generic.py
CHANGED
|
@@ -4,6 +4,7 @@ import re
|
|
|
4
4
|
|
|
5
5
|
from django.conf import settings
|
|
6
6
|
from django.contrib import messages
|
|
7
|
+
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
7
8
|
from django.contrib.contenttypes.models import ContentType
|
|
8
9
|
from django.core.exceptions import (
|
|
9
10
|
FieldDoesNotExist,
|
|
@@ -58,6 +59,14 @@ from nautobot.extras.tables import AssociatedContactsTable
|
|
|
58
59
|
from nautobot.extras.utils import remove_prefix_from_cf_key
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
class GenericView(LoginRequiredMixin, View):
|
|
63
|
+
"""
|
|
64
|
+
Base class for non-object-related views.
|
|
65
|
+
|
|
66
|
+
Enforces authentication, which Django's base View does not by default.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
|
|
61
70
|
class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
62
71
|
"""
|
|
63
72
|
Retrieve a single object for display.
|
|
@@ -68,7 +77,6 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
68
77
|
|
|
69
78
|
queryset = None
|
|
70
79
|
template_name = None
|
|
71
|
-
is_contact_associatable_model = True
|
|
72
80
|
|
|
73
81
|
def get_required_permission(self):
|
|
74
82
|
return get_permission_for_model(self.queryset.model, "view")
|
|
@@ -127,7 +135,6 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
127
135
|
content_type = ContentType.objects.get_for_model(self.queryset.model)
|
|
128
136
|
context = {
|
|
129
137
|
"object": instance,
|
|
130
|
-
"is_contact_associatable_model": self.is_contact_associatable_model,
|
|
131
138
|
"content_type": content_type,
|
|
132
139
|
"verbose_name": self.queryset.model._meta.verbose_name,
|
|
133
140
|
"verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
|
|
@@ -135,7 +142,7 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
135
142
|
"last_updated_by": last_updated_by,
|
|
136
143
|
**self.get_extra_context(request, instance),
|
|
137
144
|
}
|
|
138
|
-
if
|
|
145
|
+
if instance.is_contact_associable_model:
|
|
139
146
|
paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
|
|
140
147
|
associations = (
|
|
141
148
|
ContactAssociation.objects.filter(
|
|
@@ -220,6 +227,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
220
227
|
display_filter_params = []
|
|
221
228
|
dynamic_filter_form = None
|
|
222
229
|
filter_form = None
|
|
230
|
+
hide_hierarchy_ui = False
|
|
223
231
|
|
|
224
232
|
if self.filterset:
|
|
225
233
|
filter_params = self.get_filter_params(request)
|
|
@@ -232,6 +240,12 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
232
240
|
)
|
|
233
241
|
self.queryset = self.queryset.none()
|
|
234
242
|
|
|
243
|
+
# If a valid filterset is applied, we have to hide the hierarchy indentation in the UI for tables that support hierarchy indentation.
|
|
244
|
+
# NOTE: An empty filterset query-param is also valid filterset and we dont want to hide hierarchy indentation if no filter query-param is provided
|
|
245
|
+
# hence `filterset.data`.
|
|
246
|
+
if filterset.is_valid() and filterset.data:
|
|
247
|
+
hide_hierarchy_ui = True
|
|
248
|
+
|
|
235
249
|
display_filter_params = [
|
|
236
250
|
check_filter_for_display(filterset.filters, field_name, values)
|
|
237
251
|
for field_name, values in filter_params.items()
|
|
@@ -283,9 +297,9 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
283
297
|
table_config_form = None
|
|
284
298
|
if self.table:
|
|
285
299
|
# Construct the objects table
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
table = self.table(self.queryset, user=request.user,
|
|
300
|
+
if self.request.GET.getlist("sort"):
|
|
301
|
+
hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
|
|
302
|
+
table = self.table(self.queryset, user=request.user, hide_hierarchy_ui=hide_hierarchy_ui)
|
|
289
303
|
if "pk" in table.base_columns and (permissions["change"] or permissions["delete"]):
|
|
290
304
|
table.columns.show("pk")
|
|
291
305
|
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -226,7 +226,6 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
226
226
|
create_form_class = None
|
|
227
227
|
update_form_class = None
|
|
228
228
|
parser_classes = [FormParser, MultiPartParser]
|
|
229
|
-
is_contact_associatable_model = True
|
|
230
229
|
queryset = None
|
|
231
230
|
# serializer_class has to be specified to eliminate the need to override retrieve() in the RetrieveModelMixin for now.
|
|
232
231
|
serializer_class = None
|
|
@@ -608,6 +607,7 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
608
607
|
action_buttons = ("add", "import", "export")
|
|
609
608
|
filterset_class = None
|
|
610
609
|
filterset_form_class = None
|
|
610
|
+
hide_hierarchy_ui = False
|
|
611
611
|
non_filter_params = (
|
|
612
612
|
"export", # trigger for CSV/export-template/YAML export # 3.0 TODO: remove, irrelevant after #4746
|
|
613
613
|
"page", # used by django-tables2.RequestConfig
|
|
@@ -629,6 +629,12 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
629
629
|
format_html("Invalid filters were specified: {}", self.filterset.errors),
|
|
630
630
|
)
|
|
631
631
|
queryset = queryset.none()
|
|
632
|
+
|
|
633
|
+
# If a valid filterset is applied, we have to hide the hierarchy indentation in the UI for tables that support hierarchy indentation.
|
|
634
|
+
# NOTE: An empty filterset query-param is also valid filterset and we dont want to hide hierarchy indentation if no filter query-param is provided
|
|
635
|
+
# hence `filterset.data`.
|
|
636
|
+
if self.filterset.is_valid() and self.filterset.data:
|
|
637
|
+
self.hide_hierarchy_ui = True
|
|
632
638
|
return queryset
|
|
633
639
|
|
|
634
640
|
# 3.0 TODO: remove, irrelevant after #4746
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -65,10 +65,13 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
65
65
|
table_class = view.get_table_class()
|
|
66
66
|
request = kwargs.get("request", view.request)
|
|
67
67
|
queryset = view.alter_queryset(request)
|
|
68
|
+
|
|
68
69
|
if view.action in ["list", "notes", "changelog"]:
|
|
69
70
|
if view.action == "list":
|
|
70
71
|
permissions = kwargs.get("permissions", {})
|
|
71
|
-
|
|
72
|
+
if view.request.GET.getlist("sort"):
|
|
73
|
+
view.hide_hierarchy_ui = True # hide tree hierarchy if custom sort is used
|
|
74
|
+
table = table_class(queryset, user=request.user, hide_hierarchy_ui=view.hide_hierarchy_ui)
|
|
72
75
|
if "pk" in table.base_columns and (permissions["change"] or permissions["delete"]):
|
|
73
76
|
table.columns.show("pk")
|
|
74
77
|
elif view.action == "notes":
|
|
@@ -142,7 +145,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
142
145
|
view = renderer_context["view"]
|
|
143
146
|
request = renderer_context["request"]
|
|
144
147
|
# Check if queryset attribute is set before doing anything
|
|
145
|
-
is_contact_associatable_model = view.is_contact_associatable_model
|
|
146
148
|
queryset = view.alter_queryset(request)
|
|
147
149
|
model = queryset.model
|
|
148
150
|
form_class = view.get_form_class()
|
|
@@ -219,7 +221,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
219
221
|
|
|
220
222
|
context = {
|
|
221
223
|
"content_type": content_type,
|
|
222
|
-
"is_contact_associatable_model": is_contact_associatable_model,
|
|
223
224
|
"form": form,
|
|
224
225
|
"filter_form": filter_form,
|
|
225
226
|
"dynamic_filter_form": self.get_dynamic_filter_form(view, request, filterset_class=view.filterset_class),
|
|
@@ -241,9 +242,13 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
241
242
|
|
|
242
243
|
context["created_by"] = created_by
|
|
243
244
|
context["last_updated_by"] = last_updated_by
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
if instance.is_contact_associable_model:
|
|
246
|
+
paginate = {"paginator_class": EnhancedPaginator, "per_page": get_paginate_count(request)}
|
|
247
|
+
associations = instance.associated_contacts.restrict(request.user, "view").order_by("role__name")
|
|
248
|
+
associations_table = AssociatedContactsTable(associations, orderable=False)
|
|
249
|
+
RequestConfig(request, paginate).configure(associations_table)
|
|
250
|
+
associations_table.columns.show("pk")
|
|
251
|
+
context["associated_contacts_table"] = associations_table
|
|
247
252
|
else:
|
|
248
253
|
context["associated_contacts_table"] = None
|
|
249
254
|
context.update(view.get_extra_context(request, instance))
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -55,7 +55,7 @@ from nautobot.dcim.models import (
|
|
|
55
55
|
ConsoleServerPort,
|
|
56
56
|
ConsoleServerPortTemplate,
|
|
57
57
|
Controller,
|
|
58
|
-
|
|
58
|
+
ControllerManagedDeviceGroup,
|
|
59
59
|
Device,
|
|
60
60
|
DeviceBay,
|
|
61
61
|
DeviceBayTemplate,
|
|
@@ -598,7 +598,7 @@ class DeviceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
|
|
|
598
598
|
"secrets_group",
|
|
599
599
|
"device_redundancy_group",
|
|
600
600
|
"device_redundancy_group_priority",
|
|
601
|
-
"
|
|
601
|
+
"controller_managed_device_group",
|
|
602
602
|
]
|
|
603
603
|
},
|
|
604
604
|
},
|
|
@@ -1049,7 +1049,7 @@ class ControllerSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
|
|
|
1049
1049
|
fields = "__all__"
|
|
1050
1050
|
|
|
1051
1051
|
|
|
1052
|
-
class
|
|
1052
|
+
class ControllerManagedDeviceGroupSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
|
|
1053
1053
|
class Meta:
|
|
1054
|
-
model =
|
|
1054
|
+
model = ControllerManagedDeviceGroup
|
|
1055
1055
|
fields = "__all__"
|
nautobot/dcim/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.DCIMRootView
|
|
5
|
+
router = OrderedDefaultRouter(view_name="DCIM")
|
|
7
6
|
|
|
8
7
|
# Locations
|
|
9
8
|
router.register("location-types", views.LocationTypeViewSet)
|
|
@@ -80,7 +79,7 @@ router.register("connected-device", views.ConnectedDeviceViewSet, basename="conn
|
|
|
80
79
|
|
|
81
80
|
# Controllers
|
|
82
81
|
router.register("controllers", views.ControllerViewSet)
|
|
83
|
-
router.register("controller-device-groups", views.
|
|
82
|
+
router.register("controller-managed-device-groups", views.ControllerManagedDeviceGroupViewSet)
|
|
84
83
|
|
|
85
84
|
app_name = "dcim-api"
|
|
86
85
|
urlpatterns = router.urls
|