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
|
@@ -16,11 +16,13 @@ from nautobot.core.tables import CustomFieldColumn
|
|
|
16
16
|
from nautobot.core.testing import APITestCase, TestCase, TransactionTestCase
|
|
17
17
|
from nautobot.core.testing.models import ModelTestCases
|
|
18
18
|
from nautobot.core.testing.utils import post_data
|
|
19
|
+
from nautobot.core.utils.lookup import get_changes_for_model
|
|
19
20
|
from nautobot.dcim.filters import LocationFilterSet
|
|
20
21
|
from nautobot.dcim.forms import RackFilterForm
|
|
21
22
|
from nautobot.dcim.models import Device, Location, LocationType, Rack
|
|
22
23
|
from nautobot.dcim.tables import LocationTable
|
|
23
24
|
from nautobot.extras.choices import CustomFieldFilterLogicChoices, CustomFieldTypeChoices
|
|
25
|
+
from nautobot.extras.context_managers import web_request_context
|
|
24
26
|
from nautobot.extras.models import ComputedField, CustomField, CustomFieldChoice, Status
|
|
25
27
|
from nautobot.users.models import ObjectPermission
|
|
26
28
|
from nautobot.virtualization.models import VirtualMachine
|
|
@@ -1537,8 +1539,10 @@ class CustomFieldFilterTest(TestCase):
|
|
|
1537
1539
|
cf.save()
|
|
1538
1540
|
cf.content_types.set([obj_type])
|
|
1539
1541
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
+
cls.select_choices = (
|
|
1543
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Foo"),
|
|
1544
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Bar"),
|
|
1545
|
+
)
|
|
1542
1546
|
|
|
1543
1547
|
# Multi-select filtering
|
|
1544
1548
|
cf = CustomField(
|
|
@@ -1548,8 +1552,11 @@ class CustomFieldFilterTest(TestCase):
|
|
|
1548
1552
|
cf.save()
|
|
1549
1553
|
cf.content_types.set([obj_type])
|
|
1550
1554
|
|
|
1551
|
-
|
|
1552
|
-
|
|
1555
|
+
cls.multiselect_choices = (
|
|
1556
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Foo"),
|
|
1557
|
+
CustomFieldChoice.objects.create(custom_field=cf, value="Bar"),
|
|
1558
|
+
)
|
|
1559
|
+
|
|
1553
1560
|
cls.location_type = LocationType.objects.get(name="Campus")
|
|
1554
1561
|
location_status = Status.objects.get_for_model(Location).first()
|
|
1555
1562
|
Location.objects.create(
|
|
@@ -1844,6 +1851,10 @@ class CustomFieldFilterTest(TestCase):
|
|
|
1844
1851
|
self.filterset({"cf_cf8": ["Foo", "AR"]}, self.queryset).qs,
|
|
1845
1852
|
self.queryset.filter(_custom_field_data__cf8__in=["Foo", "AR"]),
|
|
1846
1853
|
)
|
|
1854
|
+
self.assertQuerysetEqualAndNotEmpty( # https://github.com/nautobot/nautobot/issues/5009
|
|
1855
|
+
self.filterset({"cf_cf8": [str(choice.pk) for choice in self.select_choices]}, self.queryset).qs,
|
|
1856
|
+
self.queryset.filter(_custom_field_data__cf8__in=[choice.value for choice in self.select_choices]),
|
|
1857
|
+
)
|
|
1847
1858
|
self.assertQuerysetEqual(
|
|
1848
1859
|
self.filterset({"cf_cf8__n": ["Foo"]}, self.queryset).qs,
|
|
1849
1860
|
self.queryset.exclude(_custom_field_data__cf8="Foo")
|
|
@@ -1913,6 +1924,10 @@ class CustomFieldFilterTest(TestCase):
|
|
|
1913
1924
|
self.filterset({"cf_cf9": "Bar"}, self.queryset).qs,
|
|
1914
1925
|
self.queryset.filter(_custom_field_data__cf9__contains="Bar"),
|
|
1915
1926
|
)
|
|
1927
|
+
self.assertQuerysetEqualAndNotEmpty( # https://github.com/nautobot/nautobot/issues/5009
|
|
1928
|
+
self.filterset({"cf_cf9": str(self.multiselect_choices[0].pk)}, self.queryset).qs,
|
|
1929
|
+
self.queryset.filter(_custom_field_data__cf9__contains=self.multiselect_choices[0].value),
|
|
1930
|
+
)
|
|
1916
1931
|
|
|
1917
1932
|
|
|
1918
1933
|
class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
|
|
@@ -2016,30 +2031,64 @@ class CustomFieldBackgroundTasks(TransactionTestCase):
|
|
|
2016
2031
|
|
|
2017
2032
|
self.assertEqual(location.cf["cf1"], "Foo")
|
|
2018
2033
|
|
|
2034
|
+
with web_request_context(self.user):
|
|
2035
|
+
cf = CustomField(label="CF2", type=CustomFieldTypeChoices.TYPE_TEXT, default="Bar")
|
|
2036
|
+
cf.save()
|
|
2037
|
+
cf.content_types.set([obj_type])
|
|
2038
|
+
|
|
2039
|
+
location.refresh_from_db()
|
|
2040
|
+
|
|
2041
|
+
self.assertEqual(location.cf["cf2"], "Bar")
|
|
2042
|
+
|
|
2043
|
+
oc_list = get_changes_for_model(location).order_by("pk")
|
|
2044
|
+
self.assertEqual(len(oc_list), 1)
|
|
2045
|
+
self.assertEqual(oc_list[0].changed_object, location)
|
|
2046
|
+
self.assertEqual(oc_list[0].change_context_detail, "provision custom field data for new content types")
|
|
2047
|
+
self.assertEqual(oc_list[0].user, self.user)
|
|
2048
|
+
|
|
2019
2049
|
def test_delete_custom_field_data_task(self):
|
|
2020
2050
|
obj_type = ContentType.objects.get_for_model(Location)
|
|
2021
|
-
|
|
2051
|
+
cf_1 = CustomField(
|
|
2022
2052
|
label="CF1",
|
|
2023
2053
|
type=CustomFieldTypeChoices.TYPE_TEXT,
|
|
2024
2054
|
)
|
|
2025
|
-
|
|
2026
|
-
|
|
2055
|
+
cf_1.save()
|
|
2056
|
+
cf_1.content_types.set([obj_type])
|
|
2057
|
+
cf_2 = CustomField(
|
|
2058
|
+
label="CF2",
|
|
2059
|
+
type=CustomFieldTypeChoices.TYPE_TEXT,
|
|
2060
|
+
)
|
|
2061
|
+
cf_2.save()
|
|
2062
|
+
cf_2.content_types.set([obj_type])
|
|
2027
2063
|
location_type = LocationType.objects.create(name="Root Type 2")
|
|
2028
2064
|
location_status = Status.objects.get_for_model(Location).first()
|
|
2029
2065
|
location = Location(
|
|
2030
2066
|
name="Location 1",
|
|
2031
2067
|
location_type=location_type,
|
|
2032
2068
|
status=location_status,
|
|
2033
|
-
_custom_field_data={"cf1": "foo"},
|
|
2069
|
+
_custom_field_data={"cf1": "foo", "cf2": "bar"},
|
|
2034
2070
|
)
|
|
2035
2071
|
location.save()
|
|
2036
2072
|
|
|
2037
|
-
|
|
2073
|
+
cf_1.delete()
|
|
2038
2074
|
|
|
2039
2075
|
location.refresh_from_db()
|
|
2040
2076
|
|
|
2041
2077
|
self.assertTrue("cf1" not in location.cf)
|
|
2042
2078
|
|
|
2079
|
+
with web_request_context(self.user):
|
|
2080
|
+
cf_2.delete()
|
|
2081
|
+
|
|
2082
|
+
location.refresh_from_db()
|
|
2083
|
+
|
|
2084
|
+
self.assertTrue("cf2" not in location.cf)
|
|
2085
|
+
|
|
2086
|
+
oc_list = get_changes_for_model(location).order_by("pk")
|
|
2087
|
+
self.assertEqual(len(oc_list), 1)
|
|
2088
|
+
self.assertEqual(oc_list[0].changed_object, location)
|
|
2089
|
+
self.assertEqual(oc_list[0].change_context_detail, "delete custom field data")
|
|
2090
|
+
self.assertEqual(oc_list[0].user, self.user)
|
|
2091
|
+
|
|
2043
2092
|
def test_update_custom_field_choice_data_task(self):
|
|
2044
2093
|
obj_type = ContentType.objects.get_for_model(Location)
|
|
2045
2094
|
cf = CustomField(
|
|
@@ -2068,6 +2117,20 @@ class CustomFieldBackgroundTasks(TransactionTestCase):
|
|
|
2068
2117
|
|
|
2069
2118
|
self.assertEqual(location.cf["cf1"], "Bar")
|
|
2070
2119
|
|
|
2120
|
+
with web_request_context(self.user):
|
|
2121
|
+
choice.value = "FizzBuzz"
|
|
2122
|
+
choice.save()
|
|
2123
|
+
|
|
2124
|
+
location.refresh_from_db()
|
|
2125
|
+
|
|
2126
|
+
self.assertEqual(location.cf["cf1"], "FizzBuzz")
|
|
2127
|
+
|
|
2128
|
+
oc_list = get_changes_for_model(location).order_by("pk")
|
|
2129
|
+
self.assertEqual(len(oc_list), 1)
|
|
2130
|
+
self.assertEqual(oc_list[0].changed_object, location)
|
|
2131
|
+
self.assertEqual(oc_list[0].change_context_detail, "update custom field choice data")
|
|
2132
|
+
self.assertEqual(oc_list[0].user, self.user)
|
|
2133
|
+
|
|
2071
2134
|
|
|
2072
2135
|
class CustomFieldTableTest(TestCase):
|
|
2073
2136
|
"""
|
|
@@ -16,6 +16,7 @@ from nautobot.dcim.choices import PortTypeChoices
|
|
|
16
16
|
from nautobot.dcim.filters import DeviceFilterSet
|
|
17
17
|
from nautobot.dcim.forms import DeviceFilterForm, DeviceForm
|
|
18
18
|
from nautobot.dcim.models import (
|
|
19
|
+
Controller,
|
|
19
20
|
Device,
|
|
20
21
|
DeviceType,
|
|
21
22
|
FrontPort,
|
|
@@ -48,6 +49,7 @@ from nautobot.tenancy.models import Tenant
|
|
|
48
49
|
class DynamicGroupTestBase(TestCase):
|
|
49
50
|
@classmethod
|
|
50
51
|
def setUpTestData(cls):
|
|
52
|
+
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
51
53
|
Device.objects.all().delete()
|
|
52
54
|
cls.device_ct = ContentType.objects.get_for_model(Device)
|
|
53
55
|
cls.dynamicgroup_ct = ContentType.objects.get_for_model(DynamicGroup)
|
|
@@ -61,6 +61,7 @@ from nautobot.extras.models import (
|
|
|
61
61
|
ComputedField,
|
|
62
62
|
ConfigContext,
|
|
63
63
|
Contact,
|
|
64
|
+
ContactAssociation,
|
|
64
65
|
CustomField,
|
|
65
66
|
CustomFieldChoice,
|
|
66
67
|
CustomLink,
|
|
@@ -457,7 +458,91 @@ class ContentTypeFilterSetTestCase(FilterTestCases.FilterTestCase):
|
|
|
457
458
|
)
|
|
458
459
|
|
|
459
460
|
|
|
460
|
-
class
|
|
461
|
+
class ContactAndTeamFilterSetTestCaseMixin:
|
|
462
|
+
"""Mixin class to test common filters to both Contact and Team filter sets."""
|
|
463
|
+
|
|
464
|
+
def test_similar_to_location_data(self):
|
|
465
|
+
"""Complex test to test the complex `similar_to_location_data` method filter."""
|
|
466
|
+
ContactAssociation.objects.all().delete()
|
|
467
|
+
self.queryset.delete()
|
|
468
|
+
location_type = LocationType.objects.filter(parent__isnull=True).first()
|
|
469
|
+
location_status = Status.objects.get_for_model(Location).first()
|
|
470
|
+
test_locations = (
|
|
471
|
+
Location.objects.create(
|
|
472
|
+
location_type=location_type,
|
|
473
|
+
name="Filter Test Location 0",
|
|
474
|
+
status=location_status,
|
|
475
|
+
contact_name="match 0",
|
|
476
|
+
),
|
|
477
|
+
Location.objects.create(
|
|
478
|
+
location_type=location_type,
|
|
479
|
+
name="Filter Test Location 1",
|
|
480
|
+
status=location_status,
|
|
481
|
+
contact_email="Test email for location 1 and 2",
|
|
482
|
+
),
|
|
483
|
+
Location.objects.create(
|
|
484
|
+
location_type=location_type,
|
|
485
|
+
name="Filter Test Location 2",
|
|
486
|
+
status=location_status,
|
|
487
|
+
contact_email="TEST EMAIL FOR LOCATION 1 AND 2",
|
|
488
|
+
contact_phone="Test phone for location 2 and 3",
|
|
489
|
+
),
|
|
490
|
+
Location.objects.create(
|
|
491
|
+
location_type=location_type,
|
|
492
|
+
name="Filter Test Location 3",
|
|
493
|
+
status=location_status,
|
|
494
|
+
contact_phone="Test phone for location 2 and 3",
|
|
495
|
+
),
|
|
496
|
+
Location.objects.create(
|
|
497
|
+
location_type=location_type,
|
|
498
|
+
name="Filter Test Location 4",
|
|
499
|
+
status=location_status,
|
|
500
|
+
contact_name="Hopefully this doesn't match any random factory data",
|
|
501
|
+
contact_email="Hopefully this doesn't match any random factory data",
|
|
502
|
+
contact_phone="Hopefully this doesn't match any random factory data",
|
|
503
|
+
physical_address="Hopefully this doesn't match any random factory data",
|
|
504
|
+
shipping_address="Hopefully this doesn't match any random factory data",
|
|
505
|
+
),
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
self.queryset.create(name="match 0")
|
|
509
|
+
self.queryset.create(name="match 1 and 2", email="Test email for location 1 and 2")
|
|
510
|
+
self.queryset.create(name="match 2 and 3", phone="Test phone for location 2 and 3")
|
|
511
|
+
|
|
512
|
+
# These subtests are confusing because we're trying to test the NaturalKeyOrPKMultipleChoiceFilter
|
|
513
|
+
# behavior while also testing the `similar_to_location_data` method filter behavior.
|
|
514
|
+
with self.subTest("Test name match"):
|
|
515
|
+
params = {"similar_to_location_data": [test_locations[0].pk]}
|
|
516
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
517
|
+
self.filterset(params, self.queryset).qs,
|
|
518
|
+
self.queryset.filter(name__in=["match 0"]),
|
|
519
|
+
)
|
|
520
|
+
with self.subTest("Test email match"):
|
|
521
|
+
params = {"similar_to_location_data": [test_locations[1].pk]}
|
|
522
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
523
|
+
self.filterset(params, self.queryset).qs,
|
|
524
|
+
self.queryset.filter(name__in=["match 1 and 2"]),
|
|
525
|
+
)
|
|
526
|
+
with self.subTest("Test phone match"):
|
|
527
|
+
params = {"similar_to_location_data": [test_locations[2].pk]}
|
|
528
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
529
|
+
self.filterset(params, self.queryset).qs,
|
|
530
|
+
self.queryset.filter(name__in=["match 1 and 2", "match 2 and 3"]),
|
|
531
|
+
)
|
|
532
|
+
with self.subTest("Test email and phone match"):
|
|
533
|
+
params = {"similar_to_location_data": [test_locations[1].pk, test_locations[3].name]}
|
|
534
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
535
|
+
self.filterset(params, self.queryset).qs,
|
|
536
|
+
self.queryset.filter(name__in=["match 1 and 2", "match 2 and 3"]),
|
|
537
|
+
)
|
|
538
|
+
with self.subTest("Test no match"):
|
|
539
|
+
params = {"similar_to_location_data": [test_locations[4].pk]}
|
|
540
|
+
self.assertFalse(self.filterset(params, self.queryset).qs.exists())
|
|
541
|
+
params = {"similar_to_location_data": [test_locations[4].name]}
|
|
542
|
+
self.assertFalse(self.filterset(params, self.queryset).qs.exists())
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class ContactFilterSetTestCase(ContactAndTeamFilterSetTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
461
546
|
queryset = Contact.objects.all()
|
|
462
547
|
filterset = ContactFilterSet
|
|
463
548
|
|
|
@@ -1756,7 +1841,7 @@ class TagTestCase(FilterTestCases.NameOnlyFilterTestCase):
|
|
|
1756
1841
|
self.assertEqual(self.filterset(params, self.queryset).qs.values_list("pk", flat=True)[0], value)
|
|
1757
1842
|
|
|
1758
1843
|
|
|
1759
|
-
class TeamFilterSetTestCase(FilterTestCases.FilterTestCase):
|
|
1844
|
+
class TeamFilterSetTestCase(ContactAndTeamFilterSetTestCaseMixin, FilterTestCases.FilterTestCase):
|
|
1760
1845
|
queryset = Team.objects.all()
|
|
1761
1846
|
filterset = TeamFilterSet
|
|
1762
1847
|
|
|
@@ -1838,8 +1923,8 @@ class RoleTestCase(FilterTestCases.NameOnlyFilterTestCase):
|
|
|
1838
1923
|
def test_content_types(self):
|
|
1839
1924
|
device_ct = ContentType.objects.get_for_model(Device)
|
|
1840
1925
|
rack_ct = ContentType.objects.get_for_model(Rack)
|
|
1841
|
-
device_roles = self.queryset.filter(
|
|
1842
|
-
params = {"content_types": ["dcim.device"]}
|
|
1926
|
+
device_roles = self.queryset.filter(content_types__in=[device_ct, rack_ct]).distinct()
|
|
1927
|
+
params = {"content_types": ["dcim.device", "dcim.rack"]}
|
|
1843
1928
|
self.assertQuerysetEqualAndNotEmpty(self.filterset(params, self.queryset).qs, device_roles)
|
|
1844
1929
|
|
|
1845
1930
|
rack_roles = self.queryset.filter(content_types=rack_ct)
|
|
@@ -1810,6 +1810,15 @@ class JobResultTestCase(TestCase):
|
|
|
1810
1810
|
JobResult.objects.create(name="ExampleJob2", user=None, result=lambda: 1)
|
|
1811
1811
|
self.assertEqual(str(err.exception), "Object of type function is not JSON serializable")
|
|
1812
1812
|
|
|
1813
|
+
def test_get_task(self):
|
|
1814
|
+
"""Assert bug fix for `Cannot resolve keyword 'task_id' into field` #5440"""
|
|
1815
|
+
data = {
|
|
1816
|
+
"output": "valid data",
|
|
1817
|
+
}
|
|
1818
|
+
job_result = JobResult.objects.create(name="ExampleJob1", user=None, result=data)
|
|
1819
|
+
|
|
1820
|
+
self.assertEqual(JobResult.objects.get_task(job_result.pk), job_result)
|
|
1821
|
+
|
|
1813
1822
|
|
|
1814
1823
|
class WebhookTest(ModelTestCases.BaseModelTestCase):
|
|
1815
1824
|
model = Webhook
|
|
@@ -15,7 +15,15 @@ from nautobot.core.tables import RelationshipColumn
|
|
|
15
15
|
from nautobot.core.testing import TestCase
|
|
16
16
|
from nautobot.core.testing.models import ModelTestCases
|
|
17
17
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
18
|
-
from nautobot.dcim.models import
|
|
18
|
+
from nautobot.dcim.models import (
|
|
19
|
+
Controller,
|
|
20
|
+
Device,
|
|
21
|
+
DeviceTypeToSoftwareImageFile,
|
|
22
|
+
Location,
|
|
23
|
+
LocationType,
|
|
24
|
+
Platform,
|
|
25
|
+
Rack,
|
|
26
|
+
)
|
|
19
27
|
from nautobot.dcim.tables import LocationTable
|
|
20
28
|
from nautobot.dcim.tests.test_views import create_test_device
|
|
21
29
|
from nautobot.extras.choices import RelationshipRequiredSideChoices, RelationshipSideChoices, RelationshipTypeChoices
|
|
@@ -1165,6 +1173,7 @@ class RequiredRelationshipTestMixin:
|
|
|
1165
1173
|
# Protected FK to SoftwareImageFile prevents deletion
|
|
1166
1174
|
DeviceTypeToSoftwareImageFile.objects.all().delete()
|
|
1167
1175
|
# Protected FK to SoftwareVersion prevents deletion
|
|
1176
|
+
Controller.objects.all().delete()
|
|
1168
1177
|
Device.objects.all().update(software_version=None)
|
|
1169
1178
|
|
|
1170
1179
|
# Create required relationships:
|
|
@@ -17,11 +17,21 @@ from nautobot.core.choices import ColorChoices
|
|
|
17
17
|
from nautobot.core.models.fields import slugify_dashes_to_underscores
|
|
18
18
|
from nautobot.core.testing import extract_form_failures, extract_page_body, TestCase, ViewTestCases
|
|
19
19
|
from nautobot.core.testing.utils import disable_warnings, post_data
|
|
20
|
-
from nautobot.dcim.models import
|
|
20
|
+
from nautobot.dcim.models import (
|
|
21
|
+
ConsolePort,
|
|
22
|
+
Controller,
|
|
23
|
+
Device,
|
|
24
|
+
DeviceType,
|
|
25
|
+
Interface,
|
|
26
|
+
Location,
|
|
27
|
+
LocationType,
|
|
28
|
+
Manufacturer,
|
|
29
|
+
)
|
|
21
30
|
from nautobot.dcim.tests import test_views
|
|
22
31
|
from nautobot.extras.choices import (
|
|
23
32
|
CustomFieldTypeChoices,
|
|
24
33
|
JobExecutionType,
|
|
34
|
+
LogLevelChoices,
|
|
25
35
|
ObjectChangeActionChoices,
|
|
26
36
|
SecretsGroupAccessTypeChoices,
|
|
27
37
|
SecretsGroupSecretTypeChoices,
|
|
@@ -43,6 +53,7 @@ from nautobot.extras.models import (
|
|
|
43
53
|
GraphQLQuery,
|
|
44
54
|
Job,
|
|
45
55
|
JobButton,
|
|
56
|
+
JobLogEntry,
|
|
46
57
|
JobResult,
|
|
47
58
|
Note,
|
|
48
59
|
ObjectChange,
|
|
@@ -781,6 +792,49 @@ class DynamicGroupTestCase(
|
|
|
781
792
|
"dynamic_group_memberships-MAX_NUM_FORMS": "1000",
|
|
782
793
|
}
|
|
783
794
|
|
|
795
|
+
def test_get_object_dynamic_groups_anonymous(self):
|
|
796
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
797
|
+
self.client.logout()
|
|
798
|
+
response = self.client.get(url, follow=True)
|
|
799
|
+
self.assertHttpStatus(response, 200)
|
|
800
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
801
|
+
|
|
802
|
+
def test_get_object_dynamic_groups_without_permission(self):
|
|
803
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
804
|
+
response = self.client.get(url)
|
|
805
|
+
self.assertHttpStatus(response, [403, 404])
|
|
806
|
+
|
|
807
|
+
def test_get_object_dynamic_groups_with_permission(self):
|
|
808
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
809
|
+
self.add_permissions("dcim.view_device", "extras.view_dynamicgroup")
|
|
810
|
+
response = self.client.get(url)
|
|
811
|
+
self.assertHttpStatus(response, 200)
|
|
812
|
+
response_body = response.content.decode(response.charset)
|
|
813
|
+
self.assertIn("DG 1", response_body, msg=response_body)
|
|
814
|
+
self.assertIn("DG 2", response_body, msg=response_body)
|
|
815
|
+
self.assertIn("DG 3", response_body, msg=response_body)
|
|
816
|
+
|
|
817
|
+
def test_get_object_dynamic_groups_with_constrained_permission(self):
|
|
818
|
+
self.add_permissions("extras.view_dynamicgroup")
|
|
819
|
+
obj_perm = ObjectPermission(
|
|
820
|
+
name="View a device",
|
|
821
|
+
constraints={"pk": Device.objects.first().pk},
|
|
822
|
+
actions=["view"],
|
|
823
|
+
)
|
|
824
|
+
obj_perm.save()
|
|
825
|
+
obj_perm.users.add(self.user)
|
|
826
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
|
|
827
|
+
|
|
828
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.first().pk})
|
|
829
|
+
response = self.client.get(url)
|
|
830
|
+
self.assertHttpStatus(response, 200)
|
|
831
|
+
response_body = response.content.decode(response.charset)
|
|
832
|
+
self.assertIn("DG 1", response_body, msg=response_body)
|
|
833
|
+
|
|
834
|
+
url = reverse("dcim:device_dynamicgroups", kwargs={"pk": Device.objects.last().pk})
|
|
835
|
+
response = self.client.get(url)
|
|
836
|
+
self.assertHttpStatus(response, 404)
|
|
837
|
+
|
|
784
838
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
785
839
|
def test_edit_saved_filter(self):
|
|
786
840
|
"""Test that editing a filter works using the edit view."""
|
|
@@ -936,6 +990,34 @@ class GitRepositoryTestCase(
|
|
|
936
990
|
self.form_data = form_data
|
|
937
991
|
super().test_edit_object_with_constrained_permission()
|
|
938
992
|
|
|
993
|
+
def test_post_sync_repo_anonymous(self):
|
|
994
|
+
self.client.logout()
|
|
995
|
+
url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
|
|
996
|
+
response = self.client.post(url, follow=True)
|
|
997
|
+
self.assertHttpStatus(response, 200)
|
|
998
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
999
|
+
|
|
1000
|
+
def test_post_sync_repo_without_permission(self):
|
|
1001
|
+
url = reverse("extras:gitrepository_sync", kwargs={"pk": self._get_queryset().first().pk})
|
|
1002
|
+
response = self.client.post(url)
|
|
1003
|
+
self.assertHttpStatus(response, [403, 404])
|
|
1004
|
+
|
|
1005
|
+
# TODO: mock/stub out `enqueue_pull_git_repository_and_refresh_data` and test successful POST with permissions
|
|
1006
|
+
|
|
1007
|
+
def test_post_dryrun_repo_anonymous(self):
|
|
1008
|
+
self.client.logout()
|
|
1009
|
+
url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
|
|
1010
|
+
response = self.client.post(url, follow=True)
|
|
1011
|
+
self.assertHttpStatus(response, 200)
|
|
1012
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
1013
|
+
|
|
1014
|
+
def test_post_dryrun_repo_without_permission(self):
|
|
1015
|
+
url = reverse("extras:gitrepository_dryrun", kwargs={"pk": self._get_queryset().first().pk})
|
|
1016
|
+
response = self.client.post(url)
|
|
1017
|
+
self.assertHttpStatus(response, [403, 404])
|
|
1018
|
+
|
|
1019
|
+
# TODO: mock/stub out `enqueue_git_repository_diff_origin_and_local` and test successful POST with permissions
|
|
1020
|
+
|
|
939
1021
|
|
|
940
1022
|
class NoteTestCase(
|
|
941
1023
|
ViewTestCases.CreateObjectViewTestCase,
|
|
@@ -1636,6 +1718,34 @@ class JobResultTestCase(
|
|
|
1636
1718
|
def setUpTestData(cls):
|
|
1637
1719
|
JobResult.objects.create(name="pass.TestPass")
|
|
1638
1720
|
JobResult.objects.create(name="fail.TestFail")
|
|
1721
|
+
JobLogEntry.objects.create(
|
|
1722
|
+
log_level=LogLevelChoices.LOG_INFO,
|
|
1723
|
+
job_result=JobResult.objects.first(),
|
|
1724
|
+
grouping="run",
|
|
1725
|
+
message="This is a test",
|
|
1726
|
+
)
|
|
1727
|
+
|
|
1728
|
+
def test_get_joblogentrytable_anonymous(self):
|
|
1729
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1730
|
+
self.client.logout()
|
|
1731
|
+
response = self.client.get(url, follow=True)
|
|
1732
|
+
self.assertHttpStatus(response, 200)
|
|
1733
|
+
self.assertRedirects(response, f"/login/?next={url}")
|
|
1734
|
+
|
|
1735
|
+
def test_get_joblogentrytable_without_permission(self):
|
|
1736
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1737
|
+
response = self.client.get(url)
|
|
1738
|
+
self.assertHttpStatus(response, [403, 404])
|
|
1739
|
+
|
|
1740
|
+
def test_get_joblogentrytable_with_permission(self):
|
|
1741
|
+
url = reverse("extras:jobresult_log-table", kwargs={"pk": JobResult.objects.first().pk})
|
|
1742
|
+
self.add_permissions("extras.view_jobresult", "extras.view_joblogentry")
|
|
1743
|
+
response = self.client.get(url)
|
|
1744
|
+
self.assertHttpStatus(response, 200)
|
|
1745
|
+
response_body = response.content.decode(response.charset)
|
|
1746
|
+
self.assertIn("This is a test", response_body)
|
|
1747
|
+
|
|
1748
|
+
# TODO test with constrained permissions on both JobResult and JobLogEntry records
|
|
1639
1749
|
|
|
1640
1750
|
|
|
1641
1751
|
class JobTestCase(
|
|
@@ -2488,6 +2598,7 @@ class RelationshipTestCase(
|
|
|
2488
2598
|
)
|
|
2489
2599
|
|
|
2490
2600
|
# Try deleting all devices and then editing the 6 VLANs (fails):
|
|
2601
|
+
Controller.objects.filter(controller_device__isnull=False).delete()
|
|
2491
2602
|
Device.objects.all().delete()
|
|
2492
2603
|
response = self.client.post(
|
|
2493
2604
|
reverse("ipam:vlan_bulk_edit"), data={"pk": [str(vlan.id) for vlan in vlans], "_apply": [""]}
|
nautobot/extras/views.py
CHANGED
|
@@ -40,6 +40,7 @@ from nautobot.core.views.viewsets import NautobotUIViewSet
|
|
|
40
40
|
from nautobot.dcim.models import Controller, Device, Interface, Location, Rack
|
|
41
41
|
from nautobot.dcim.tables import ControllerTable, DeviceTable, RackTable
|
|
42
42
|
from nautobot.extras.constants import JOB_OVERRIDABLE_FIELDS
|
|
43
|
+
from nautobot.extras.signals import change_context_state
|
|
43
44
|
from nautobot.extras.tasks import delete_custom_field_data
|
|
44
45
|
from nautobot.extras.utils import get_base_template, get_worker_count
|
|
45
46
|
from nautobot.ipam.models import IPAddress, Prefix, VLAN
|
|
@@ -369,7 +370,6 @@ class ContactUIViewSet(NautobotUIViewSet):
|
|
|
369
370
|
queryset = Contact.objects.all()
|
|
370
371
|
serializer_class = serializers.ContactSerializer
|
|
371
372
|
table_class = tables.ContactTable
|
|
372
|
-
is_contact_associatable_model = False
|
|
373
373
|
|
|
374
374
|
def get_extra_context(self, request, instance):
|
|
375
375
|
context = super().get_extra_context(request, instance)
|
|
@@ -677,8 +677,14 @@ class CustomFieldBulkDeleteView(generic.BulkDeleteView):
|
|
|
677
677
|
"""
|
|
678
678
|
Helper method to construct a list of celery tasks to execute when bulk deleting custom fields.
|
|
679
679
|
"""
|
|
680
|
+
change_context = change_context_state.get()
|
|
681
|
+
if change_context is None:
|
|
682
|
+
context = None
|
|
683
|
+
else:
|
|
684
|
+
context = change_context.as_dict(queryset)
|
|
685
|
+
context["context_detail"] = "bulk delete custom field data"
|
|
680
686
|
tasks = [
|
|
681
|
-
delete_custom_field_data.si(obj.key, set(obj.content_types.values_list("pk", flat=True)))
|
|
687
|
+
delete_custom_field_data.si(obj.key, set(obj.content_types.values_list("pk", flat=True)), context)
|
|
682
688
|
for obj in queryset
|
|
683
689
|
]
|
|
684
690
|
return tasks
|
|
@@ -917,7 +923,7 @@ class DynamicGroupBulkDeleteView(generic.BulkDeleteView):
|
|
|
917
923
|
filterset = filters.DynamicGroupFilterSet
|
|
918
924
|
|
|
919
925
|
|
|
920
|
-
class ObjectDynamicGroupsView(
|
|
926
|
+
class ObjectDynamicGroupsView(generic.GenericView):
|
|
921
927
|
"""
|
|
922
928
|
Present a list of dynamic groups associated to a particular object.
|
|
923
929
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -1116,18 +1122,18 @@ def check_and_call_git_repository_function(request, pk, func):
|
|
|
1116
1122
|
messages.error(request, "Unable to run job: Celery worker process not running.")
|
|
1117
1123
|
return redirect(request.get_full_path(), permanent=False)
|
|
1118
1124
|
else:
|
|
1119
|
-
repository = get_object_or_404(GitRepository, pk=pk)
|
|
1125
|
+
repository = get_object_or_404(GitRepository.objects.restrict(request.user, "change"), pk=pk)
|
|
1120
1126
|
job_result = func(repository, request.user)
|
|
1121
1127
|
|
|
1122
1128
|
return redirect(job_result.get_absolute_url())
|
|
1123
1129
|
|
|
1124
1130
|
|
|
1125
|
-
class GitRepositorySyncView(
|
|
1131
|
+
class GitRepositorySyncView(generic.GenericView):
|
|
1126
1132
|
def post(self, request, pk):
|
|
1127
1133
|
return check_and_call_git_repository_function(request, pk, enqueue_pull_git_repository_and_refresh_data)
|
|
1128
1134
|
|
|
1129
1135
|
|
|
1130
|
-
class GitRepositoryDryRunView(
|
|
1136
|
+
class GitRepositoryDryRunView(generic.GenericView):
|
|
1131
1137
|
def post(self, request, pk):
|
|
1132
1138
|
return check_and_call_git_repository_function(request, pk, enqueue_git_repository_diff_origin_and_local)
|
|
1133
1139
|
|
|
@@ -1806,7 +1812,7 @@ class JobResultView(generic.ObjectView):
|
|
|
1806
1812
|
}
|
|
1807
1813
|
|
|
1808
1814
|
|
|
1809
|
-
class JobLogEntryTableView(
|
|
1815
|
+
class JobLogEntryTableView(generic.GenericView):
|
|
1810
1816
|
"""
|
|
1811
1817
|
Display a table of `JobLogEntry` objects for a given `JobResult` instance.
|
|
1812
1818
|
"""
|
|
@@ -1814,7 +1820,7 @@ class JobLogEntryTableView(View):
|
|
|
1814
1820
|
queryset = JobResult.objects.all()
|
|
1815
1821
|
|
|
1816
1822
|
def get(self, request, pk=None):
|
|
1817
|
-
instance = self.queryset.
|
|
1823
|
+
instance = get_object_or_404(self.queryset.restrict(request.user, "view"), pk=pk)
|
|
1818
1824
|
filter_q = request.GET.get("q")
|
|
1819
1825
|
if filter_q:
|
|
1820
1826
|
queryset = instance.job_log_entries.filter(
|
|
@@ -1893,7 +1899,7 @@ class ObjectChangeView(generic.ObjectView):
|
|
|
1893
1899
|
}
|
|
1894
1900
|
|
|
1895
1901
|
|
|
1896
|
-
class ObjectChangeLogView(
|
|
1902
|
+
class ObjectChangeLogView(generic.GenericView):
|
|
1897
1903
|
"""
|
|
1898
1904
|
Present a history of changes made to a particular object.
|
|
1899
1905
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -1939,8 +1945,6 @@ class ObjectChangeLogView(View):
|
|
|
1939
1945
|
"table": objectchanges_table,
|
|
1940
1946
|
"base_template": self.base_template,
|
|
1941
1947
|
"active_tab": "changelog",
|
|
1942
|
-
# Currently only Contact and Team models are not contact_associatable.
|
|
1943
|
-
"is_contact_associatable_model": type(obj) not in [Contact, Team],
|
|
1944
1948
|
},
|
|
1945
1949
|
)
|
|
1946
1950
|
|
|
@@ -1979,7 +1983,7 @@ class NoteDeleteView(generic.ObjectDeleteView):
|
|
|
1979
1983
|
queryset = Note.objects.all()
|
|
1980
1984
|
|
|
1981
1985
|
|
|
1982
|
-
class ObjectNotesView(
|
|
1986
|
+
class ObjectNotesView(generic.GenericView):
|
|
1983
1987
|
"""
|
|
1984
1988
|
Present a list of notes associated to a particular object.
|
|
1985
1989
|
base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
|
|
@@ -2000,7 +2004,7 @@ class ObjectNotesView(View):
|
|
|
2000
2004
|
"assigned_object_id": obj.pk,
|
|
2001
2005
|
}
|
|
2002
2006
|
)
|
|
2003
|
-
notes_table = tables.NoteTable(obj.notes)
|
|
2007
|
+
notes_table = tables.NoteTable(obj.notes.restrict(request.user, "view"))
|
|
2004
2008
|
|
|
2005
2009
|
# Apply the request context
|
|
2006
2010
|
paginate = {
|
|
@@ -2022,8 +2026,6 @@ class ObjectNotesView(View):
|
|
|
2022
2026
|
"base_template": self.base_template,
|
|
2023
2027
|
"active_tab": "notes",
|
|
2024
2028
|
"form": notes_form,
|
|
2025
|
-
# Currently only Contact and Team models are not contact_associatable.
|
|
2026
|
-
"is_contact_associatable_model": type(obj) not in [Contact, Team],
|
|
2027
2029
|
},
|
|
2028
2030
|
)
|
|
2029
2031
|
|
|
@@ -2241,7 +2243,7 @@ class SecretView(generic.ObjectView):
|
|
|
2241
2243
|
}
|
|
2242
2244
|
|
|
2243
2245
|
|
|
2244
|
-
class SecretProviderParametersFormView(
|
|
2246
|
+
class SecretProviderParametersFormView(generic.GenericView):
|
|
2245
2247
|
"""
|
|
2246
2248
|
Helper view to SecretView; retrieve the HTML form appropriate for entering parameters for a given SecretsProvider.
|
|
2247
2249
|
"""
|
|
@@ -2532,7 +2534,6 @@ class TeamUIViewSet(NautobotUIViewSet):
|
|
|
2532
2534
|
queryset = Team.objects.all()
|
|
2533
2535
|
serializer_class = serializers.TeamSerializer
|
|
2534
2536
|
table_class = tables.TeamTable
|
|
2535
|
-
is_contact_associatable_model = False
|
|
2536
2537
|
|
|
2537
2538
|
def get_extra_context(self, request, instance):
|
|
2538
2539
|
context = super().get_extra_context(request, instance)
|
nautobot/ipam/api/serializers.py
CHANGED
|
@@ -495,6 +495,7 @@ class ServiceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
|
|
|
495
495
|
class Meta:
|
|
496
496
|
model = Service
|
|
497
497
|
fields = "__all__"
|
|
498
|
+
validators = []
|
|
498
499
|
extra_kwargs = {
|
|
499
500
|
"device": {"help_text": "Required if no virtual_machine is specified"},
|
|
500
501
|
"virtual_machine": {"help_text": "Required if no device is specified"},
|
|
@@ -504,3 +505,12 @@ class ServiceSerializer(NautobotModelSerializer, TaggedModelSerializerMixin):
|
|
|
504
505
|
# `device`.
|
|
505
506
|
# list_display_fields = ["name", "parent", "protocol", "ports", "description"]
|
|
506
507
|
list_display_fields = ["name", "device", "protocol", "ports", "description"]
|
|
508
|
+
|
|
509
|
+
def validate(self, data):
|
|
510
|
+
if data.get("device"):
|
|
511
|
+
validator = UniqueTogetherValidator(queryset=Service.objects.all(), fields=("name", "device"))
|
|
512
|
+
validator(data, self)
|
|
513
|
+
if data.get("virtual_machine"):
|
|
514
|
+
validator = UniqueTogetherValidator(queryset=Service.objects.all(), fields=("name", "virtual_machine"))
|
|
515
|
+
validator(data, self)
|
|
516
|
+
return super().validate(data)
|
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)
|