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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from textwrap import dedent, indent
|
|
2
|
+
|
|
3
|
+
from django.core.exceptions import ValidationError
|
|
4
|
+
from django.core.management.base import BaseCommand
|
|
5
|
+
from django.db import transaction
|
|
6
|
+
|
|
7
|
+
from nautobot.dcim.models import Location
|
|
8
|
+
from nautobot.extras.filters import ContactFilterSet, TeamFilterSet
|
|
9
|
+
from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Team
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Command(BaseCommand):
|
|
13
|
+
help = "Migrate Location contact fields to Contact and Team objects."
|
|
14
|
+
verbose_help = """
|
|
15
|
+
This command will present a series of prompts to guide you through migrating Locations that
|
|
16
|
+
have data in the `contact_name`, `contact_phone`, or `contact_email` fields which are not
|
|
17
|
+
already associated to a Contact or Team. This command will give you the option to create new
|
|
18
|
+
Contacts or Teams or, if a similar Contact or Team already exists, to link the Location to the
|
|
19
|
+
existing Contact or Team. Note that when assigning a Location to an existing Contact or Team
|
|
20
|
+
that has a blank `phone` or `email` field, the value from the Location will be copied to the
|
|
21
|
+
Contact/Team. After a Location has been associated to a Contact or Team, the `contact_name`,
|
|
22
|
+
`contact_phone`, and `contact_email` fields will be cleared from the Location.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def handle(self, *args, **kwargs):
|
|
26
|
+
status_role_err_msg = "No {0} found for the ContactAssociation content type. Please ensure {0} are created before running this command."
|
|
27
|
+
if not Status.objects.get_for_model(ContactAssociation).exists():
|
|
28
|
+
self.stdout.write(self.style.ERROR(status_role_err_msg.format("statuses")))
|
|
29
|
+
return
|
|
30
|
+
if not Role.objects.get_for_model(ContactAssociation).exists():
|
|
31
|
+
self.stdout.write(self.style.ERROR(status_role_err_msg.format("roles")))
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
self.stdout.write(self.style.NOTICE(" ".join(dedent(self.verbose_help).split("\n"))))
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with transaction.atomic():
|
|
38
|
+
try:
|
|
39
|
+
self.migrate_location_contacts()
|
|
40
|
+
except KeyboardInterrupt:
|
|
41
|
+
while True:
|
|
42
|
+
rollback = input("\nRoll back changes? [y/n]: ").strip().lower()
|
|
43
|
+
if rollback == "y":
|
|
44
|
+
raise
|
|
45
|
+
elif rollback == "n":
|
|
46
|
+
break
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
self.stdout.write(self.style.ERROR("\nOperation cancelled, all changes rolled back."))
|
|
49
|
+
except:
|
|
50
|
+
self.stdout.write(self.style.ERROR("\nOperation failed, all changes rolled back."))
|
|
51
|
+
raise
|
|
52
|
+
|
|
53
|
+
def migrate_location_contacts(self):
|
|
54
|
+
"""Iterate through Locations with contact information and try to match to existing Contact or Team."""
|
|
55
|
+
locations_with_contact_data = (
|
|
56
|
+
Location.objects.without_tree_fields()
|
|
57
|
+
.exclude(
|
|
58
|
+
contact_name="",
|
|
59
|
+
contact_phone="",
|
|
60
|
+
contact_email="",
|
|
61
|
+
)
|
|
62
|
+
.filter(associated_contacts__isnull=True)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for location in locations_with_contact_data:
|
|
66
|
+
self.stdout.write(f"Finding existing Contacts or Teams for location {location.display}...")
|
|
67
|
+
selected_contact = None
|
|
68
|
+
similar_contacts = list(ContactFilterSet(data={"similar_to_location_data": [location]}).qs)
|
|
69
|
+
similar_teams = list(TeamFilterSet(data={"similar_to_location_data": [location]}).qs)
|
|
70
|
+
similar_contacts_and_teams = similar_contacts + similar_teams
|
|
71
|
+
|
|
72
|
+
if not similar_contacts_and_teams:
|
|
73
|
+
self.stdout.write(
|
|
74
|
+
self.style.WARNING(f"No similar Contacts or Teams found for location {location.display}.")
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
# Found similar contacts or teams, prompt user for action
|
|
79
|
+
self.stdout.write("")
|
|
80
|
+
self.stdout.write(self.style.WARNING(f"Found similar contacts/teams for location {location.display}:"))
|
|
81
|
+
self.stdout.write(f" current contact name: {location.contact_name!r}")
|
|
82
|
+
self.stdout.write(f" current contact phone: {location.contact_phone!r}")
|
|
83
|
+
self.stdout.write(f" current contact email: {location.contact_email!r}")
|
|
84
|
+
self.stdout.write("")
|
|
85
|
+
|
|
86
|
+
# Output menu of choices of valid contacts/teams
|
|
87
|
+
for i, contact in enumerate(similar_contacts_and_teams, start=1):
|
|
88
|
+
self.stdout.write(f"{self.style.WARNING(i)}: {contact._meta.model_name.title()}: {contact.name}")
|
|
89
|
+
self.print_contact_fields(contact, rjust=len(str(i)) + len(contact._meta.model_name) + 2)
|
|
90
|
+
self.stdout.write("")
|
|
91
|
+
|
|
92
|
+
self.stdout.write(self.style.WARNING("c") + ": Create a new Contact")
|
|
93
|
+
self.stdout.write(self.style.WARNING("t") + ": Create a new Team")
|
|
94
|
+
self.stdout.write(self.style.WARNING("s") + ": Skip this location")
|
|
95
|
+
|
|
96
|
+
# Retrieve desired contact/team from user input
|
|
97
|
+
while True:
|
|
98
|
+
choice = input("Select a choice from the list of items: ")
|
|
99
|
+
if choice == "s":
|
|
100
|
+
self.stdout.write(f"Skipping location {location.display}")
|
|
101
|
+
break
|
|
102
|
+
elif choice == "c":
|
|
103
|
+
selected_contact = self.create_new_contact_from_location(location, model=Contact)
|
|
104
|
+
break
|
|
105
|
+
elif choice == "t":
|
|
106
|
+
selected_contact = self.create_new_contact_from_location(location, model=Team)
|
|
107
|
+
break
|
|
108
|
+
elif choice.lower() == "q":
|
|
109
|
+
raise KeyboardInterrupt
|
|
110
|
+
elif choice.isdigit() and 0 < int(choice) <= len(similar_contacts_and_teams):
|
|
111
|
+
selected_contact = similar_contacts_and_teams[int(choice) - 1]
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if selected_contact is not None:
|
|
115
|
+
self.associate_contact_to_location(selected_contact, location)
|
|
116
|
+
|
|
117
|
+
def associate_contact_to_location(self, contact, location):
|
|
118
|
+
role, status = self.prompt_for_role_and_status()
|
|
119
|
+
|
|
120
|
+
# If email or phone fields are present in the location but not the contact, update the contact fields
|
|
121
|
+
updated_fields = {}
|
|
122
|
+
if location.contact_phone and not contact.phone:
|
|
123
|
+
contact.phone = location.contact_phone
|
|
124
|
+
updated_fields["phone"] = location.contact_phone
|
|
125
|
+
if location.contact_email and not contact.email:
|
|
126
|
+
contact.email = location.contact_email
|
|
127
|
+
updated_fields["email"] = location.contact_email
|
|
128
|
+
if updated_fields:
|
|
129
|
+
try:
|
|
130
|
+
contact.validated_save()
|
|
131
|
+
self.stdout.write(
|
|
132
|
+
self.style.SUCCESS(
|
|
133
|
+
f"Updated {contact._meta.model_name.title()} {contact.name} field(s): {updated_fields!r}"
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
except ValidationError as e:
|
|
137
|
+
contact.refresh_from_db()
|
|
138
|
+
self.stdout.write(
|
|
139
|
+
self.style.ERROR(f"Attempted to update {contact!r} field(s) but failed: {updated_fields!r}: {e}")
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
contact_association = ContactAssociation(
|
|
144
|
+
contact=contact if isinstance(contact, Contact) else None,
|
|
145
|
+
team=contact if isinstance(contact, Team) else None,
|
|
146
|
+
associated_object=location,
|
|
147
|
+
role=role,
|
|
148
|
+
status=status,
|
|
149
|
+
)
|
|
150
|
+
contact_association.validated_save()
|
|
151
|
+
location.contact_name = ""
|
|
152
|
+
location.contact_email = ""
|
|
153
|
+
location.contact_phone = ""
|
|
154
|
+
location.validated_save()
|
|
155
|
+
except Exception as e:
|
|
156
|
+
self.stdout.write(self.style.ERROR(f"Failed to create association: {e}"))
|
|
157
|
+
else:
|
|
158
|
+
self.stdout.write(self.style.SUCCESS(f"Associated {contact!r} to location {location.display}"))
|
|
159
|
+
|
|
160
|
+
def prompt_for_role_and_status(self):
|
|
161
|
+
# Prompt for role
|
|
162
|
+
self.stdout.write("\nValid roles for this association:")
|
|
163
|
+
valid_roles = list(Role.objects.get_for_model(ContactAssociation))
|
|
164
|
+
for i, role in enumerate(valid_roles, start=1):
|
|
165
|
+
self.stdout.write(self.style.WARNING(f"{i}") + f": {role}")
|
|
166
|
+
while True:
|
|
167
|
+
selected_role = input("Select a role for this association: ")
|
|
168
|
+
if selected_role.isdigit() and 0 < int(selected_role) <= len(valid_roles):
|
|
169
|
+
role = valid_roles[int(selected_role) - 1]
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
# Prompt for status
|
|
173
|
+
self.stdout.write("\nValid statuses for this association:")
|
|
174
|
+
valid_statuses = list(Status.objects.get_for_model(ContactAssociation))
|
|
175
|
+
for i, status in enumerate(valid_statuses, start=1):
|
|
176
|
+
self.stdout.write(self.style.WARNING(f"{i}") + f": {status}")
|
|
177
|
+
while True:
|
|
178
|
+
selected_status = input("Select a status for this association: ")
|
|
179
|
+
if selected_status.isdigit() and 0 < int(selected_status) <= len(valid_statuses):
|
|
180
|
+
status = valid_statuses[int(selected_status) - 1]
|
|
181
|
+
break
|
|
182
|
+
|
|
183
|
+
return role, status
|
|
184
|
+
|
|
185
|
+
def create_new_contact_from_location(self, location, model):
|
|
186
|
+
"""Create a new Contact or Team from the location's contact data."""
|
|
187
|
+
|
|
188
|
+
self.stdout.write(f"Creating new {model._meta.model_name.title()} for location {location.display}...")
|
|
189
|
+
name = location.contact_name
|
|
190
|
+
phone = location.contact_phone
|
|
191
|
+
email = location.contact_email
|
|
192
|
+
self.stdout.write(f" contact name: {name!r}")
|
|
193
|
+
self.stdout.write(f" contact phone: {phone!r}")
|
|
194
|
+
self.stdout.write(f" contact email: {email!r}")
|
|
195
|
+
|
|
196
|
+
while not name:
|
|
197
|
+
name = input(f"Name is required. Enter a name for the new {model._meta.model_name.title()}: ")
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
contact = model(
|
|
201
|
+
name=name,
|
|
202
|
+
phone=phone,
|
|
203
|
+
email=email,
|
|
204
|
+
)
|
|
205
|
+
contact.validated_save()
|
|
206
|
+
return contact
|
|
207
|
+
except Exception as e:
|
|
208
|
+
self.stdout.write(self.style.ERROR(f"Failed to create {model._meta.model_name.title()}: {e}"))
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
def print_contact_fields(self, contact, rjust=0):
|
|
212
|
+
for field_name in ["phone", "email"]:
|
|
213
|
+
if getattr(contact, field_name):
|
|
214
|
+
self.stdout.write(
|
|
215
|
+
field_name.title().rjust(rjust)
|
|
216
|
+
+ ": "
|
|
217
|
+
+ indent(getattr(contact, field_name), " " * (rjust + 2)).lstrip()
|
|
218
|
+
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Generated by Django 3.2.24 on 2024-03-
|
|
1
|
+
# Generated by Django 3.2.24 on 2024-03-22 15:36
|
|
2
2
|
|
|
3
3
|
import uuid
|
|
4
4
|
|
|
@@ -9,16 +9,13 @@ import django.db.models.deletion
|
|
|
9
9
|
import nautobot.core.models.fields
|
|
10
10
|
import nautobot.core.models.tree_queries
|
|
11
11
|
import nautobot.extras.models.mixins
|
|
12
|
-
import nautobot.extras.models.models
|
|
13
12
|
import nautobot.extras.models.roles
|
|
14
13
|
import nautobot.extras.models.statuses
|
|
15
|
-
import nautobot.extras.utils
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
class Migration(migrations.Migration):
|
|
19
17
|
dependencies = [
|
|
20
18
|
("extras", "0106_populate_default_statuses_and_roles_for_contact_associations"),
|
|
21
|
-
("contenttypes", "0002_remove_content_type_name"),
|
|
22
19
|
("tenancy", "0009_update_all_charfields_max_length_to_255"),
|
|
23
20
|
("dcim", "0056_update_all_charfields_max_length_to_255"),
|
|
24
21
|
]
|
|
@@ -39,29 +36,24 @@ class Migration(migrations.Migration):
|
|
|
39
36
|
"_custom_field_data",
|
|
40
37
|
models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
|
41
38
|
),
|
|
42
|
-
(
|
|
43
|
-
"local_config_context_data",
|
|
44
|
-
models.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
|
|
45
|
-
),
|
|
46
|
-
("local_config_context_data_owner_object_id", models.UUIDField(blank=True, default=None, null=True)),
|
|
47
39
|
("name", models.CharField(max_length=255, unique=True)),
|
|
48
40
|
("description", models.CharField(blank=True, max_length=255)),
|
|
49
41
|
(
|
|
50
|
-
"
|
|
42
|
+
"controller_device",
|
|
51
43
|
models.ForeignKey(
|
|
52
44
|
blank=True,
|
|
53
45
|
null=True,
|
|
54
|
-
on_delete=django.db.models.deletion.
|
|
46
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
55
47
|
related_name="controllers",
|
|
56
48
|
to="dcim.device",
|
|
57
49
|
),
|
|
58
50
|
),
|
|
59
51
|
(
|
|
60
|
-
"
|
|
52
|
+
"controller_device_redundancy_group",
|
|
61
53
|
models.ForeignKey(
|
|
62
54
|
blank=True,
|
|
63
55
|
null=True,
|
|
64
|
-
on_delete=django.db.models.deletion.
|
|
56
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
65
57
|
related_name="controllers",
|
|
66
58
|
to="dcim.deviceredundancygroup",
|
|
67
59
|
),
|
|
@@ -71,33 +63,11 @@ class Migration(migrations.Migration):
|
|
|
71
63
|
models.ForeignKey(
|
|
72
64
|
blank=True,
|
|
73
65
|
null=True,
|
|
74
|
-
on_delete=django.db.models.deletion.
|
|
66
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
75
67
|
related_name="controllers",
|
|
76
68
|
to="extras.externalintegration",
|
|
77
69
|
),
|
|
78
70
|
),
|
|
79
|
-
(
|
|
80
|
-
"local_config_context_data_owner_content_type",
|
|
81
|
-
nautobot.core.models.fields.ForeignKeyWithAutoRelatedName(
|
|
82
|
-
blank=True,
|
|
83
|
-
default=None,
|
|
84
|
-
limit_choices_to=nautobot.extras.utils.FeatureQuery("config_context_owners"),
|
|
85
|
-
null=True,
|
|
86
|
-
on_delete=django.db.models.deletion.CASCADE,
|
|
87
|
-
related_name="controllers",
|
|
88
|
-
to="contenttypes.contenttype",
|
|
89
|
-
),
|
|
90
|
-
),
|
|
91
|
-
(
|
|
92
|
-
"local_config_context_schema",
|
|
93
|
-
nautobot.core.models.fields.ForeignKeyWithAutoRelatedName(
|
|
94
|
-
blank=True,
|
|
95
|
-
null=True,
|
|
96
|
-
on_delete=django.db.models.deletion.SET_NULL,
|
|
97
|
-
related_name="controllers",
|
|
98
|
-
to="extras.configcontextschema",
|
|
99
|
-
),
|
|
100
|
-
),
|
|
101
71
|
(
|
|
102
72
|
"location",
|
|
103
73
|
models.ForeignKey(
|
|
@@ -149,11 +119,10 @@ class Migration(migrations.Migration):
|
|
|
149
119
|
models.Model,
|
|
150
120
|
nautobot.extras.models.mixins.DynamicGroupMixin,
|
|
151
121
|
nautobot.extras.models.mixins.NotesMixin,
|
|
152
|
-
nautobot.extras.models.models.ConfigContextSchemaValidationMixin,
|
|
153
122
|
),
|
|
154
123
|
),
|
|
155
124
|
migrations.CreateModel(
|
|
156
|
-
name="
|
|
125
|
+
name="ControllerManagedDeviceGroup",
|
|
157
126
|
fields=[
|
|
158
127
|
(
|
|
159
128
|
"id",
|
|
@@ -167,43 +136,16 @@ class Migration(migrations.Migration):
|
|
|
167
136
|
"_custom_field_data",
|
|
168
137
|
models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
|
169
138
|
),
|
|
170
|
-
(
|
|
171
|
-
"local_config_context_data",
|
|
172
|
-
models.JSONField(blank=True, encoder=django.core.serializers.json.DjangoJSONEncoder, null=True),
|
|
173
|
-
),
|
|
174
|
-
("local_config_context_data_owner_object_id", models.UUIDField(blank=True, default=None, null=True)),
|
|
175
139
|
("name", models.CharField(max_length=255, unique=True)),
|
|
176
140
|
("weight", models.PositiveIntegerField(default=1000)),
|
|
177
141
|
(
|
|
178
142
|
"controller",
|
|
179
143
|
models.ForeignKey(
|
|
180
144
|
on_delete=django.db.models.deletion.CASCADE,
|
|
181
|
-
related_name="
|
|
145
|
+
related_name="controller_managed_device_groups",
|
|
182
146
|
to="dcim.controller",
|
|
183
147
|
),
|
|
184
148
|
),
|
|
185
|
-
(
|
|
186
|
-
"local_config_context_data_owner_content_type",
|
|
187
|
-
nautobot.core.models.fields.ForeignKeyWithAutoRelatedName(
|
|
188
|
-
blank=True,
|
|
189
|
-
default=None,
|
|
190
|
-
limit_choices_to=nautobot.extras.utils.FeatureQuery("config_context_owners"),
|
|
191
|
-
null=True,
|
|
192
|
-
on_delete=django.db.models.deletion.CASCADE,
|
|
193
|
-
related_name="controller_device_groups",
|
|
194
|
-
to="contenttypes.contenttype",
|
|
195
|
-
),
|
|
196
|
-
),
|
|
197
|
-
(
|
|
198
|
-
"local_config_context_schema",
|
|
199
|
-
nautobot.core.models.fields.ForeignKeyWithAutoRelatedName(
|
|
200
|
-
blank=True,
|
|
201
|
-
null=True,
|
|
202
|
-
on_delete=django.db.models.deletion.SET_NULL,
|
|
203
|
-
related_name="controller_device_groups",
|
|
204
|
-
to="extras.configcontextschema",
|
|
205
|
-
),
|
|
206
|
-
),
|
|
207
149
|
(
|
|
208
150
|
"parent",
|
|
209
151
|
models.ForeignKey(
|
|
@@ -211,7 +153,7 @@ class Migration(migrations.Migration):
|
|
|
211
153
|
null=True,
|
|
212
154
|
on_delete=django.db.models.deletion.CASCADE,
|
|
213
155
|
related_name="children",
|
|
214
|
-
to="dcim.
|
|
156
|
+
to="dcim.controllermanageddevicegroup",
|
|
215
157
|
),
|
|
216
158
|
),
|
|
217
159
|
("tags", nautobot.core.models.fields.TagsField(through="extras.TaggedItem", to="extras.Tag")),
|
|
@@ -223,7 +165,6 @@ class Migration(migrations.Migration):
|
|
|
223
165
|
models.Model,
|
|
224
166
|
nautobot.extras.models.mixins.DynamicGroupMixin,
|
|
225
167
|
nautobot.extras.models.mixins.NotesMixin,
|
|
226
|
-
nautobot.extras.models.models.ConfigContextSchemaValidationMixin,
|
|
227
168
|
),
|
|
228
169
|
managers=[
|
|
229
170
|
("objects", nautobot.core.models.tree_queries.TreeManager()),
|
|
@@ -231,13 +172,13 @@ class Migration(migrations.Migration):
|
|
|
231
172
|
),
|
|
232
173
|
migrations.AddField(
|
|
233
174
|
model_name="device",
|
|
234
|
-
name="
|
|
175
|
+
name="controller_managed_device_group",
|
|
235
176
|
field=models.ForeignKey(
|
|
236
177
|
blank=True,
|
|
237
178
|
null=True,
|
|
238
179
|
on_delete=django.db.models.deletion.SET_NULL,
|
|
239
180
|
related_name="devices",
|
|
240
|
-
to="dcim.
|
|
181
|
+
to="dcim.controllermanageddevicegroup",
|
|
241
182
|
),
|
|
242
183
|
),
|
|
243
184
|
]
|
nautobot/dcim/models/__init__.py
CHANGED
|
@@ -27,7 +27,7 @@ from .device_components import (
|
|
|
27
27
|
)
|
|
28
28
|
from .devices import (
|
|
29
29
|
Controller,
|
|
30
|
-
|
|
30
|
+
ControllerManagedDeviceGroup,
|
|
31
31
|
Device,
|
|
32
32
|
DeviceFamily,
|
|
33
33
|
DeviceRedundancyGroup,
|
|
@@ -53,7 +53,7 @@ __all__ = (
|
|
|
53
53
|
"ConsoleServerPort",
|
|
54
54
|
"ConsoleServerPortTemplate",
|
|
55
55
|
"Controller",
|
|
56
|
-
"
|
|
56
|
+
"ControllerManagedDeviceGroup",
|
|
57
57
|
"Device",
|
|
58
58
|
"DeviceBay",
|
|
59
59
|
"DeviceBayTemplate",
|
nautobot/dcim/models/devices.py
CHANGED
|
@@ -43,7 +43,7 @@ from .device_components import (
|
|
|
43
43
|
|
|
44
44
|
__all__ = (
|
|
45
45
|
"Controller",
|
|
46
|
-
"
|
|
46
|
+
"ControllerManagedDeviceGroup",
|
|
47
47
|
"Device",
|
|
48
48
|
"DeviceRedundancyGroup",
|
|
49
49
|
"DeviceType",
|
|
@@ -592,8 +592,8 @@ class Device(PrimaryModel, ConfigContextModel):
|
|
|
592
592
|
verbose_name="Software Image Files",
|
|
593
593
|
help_text="Override the software image files associated with the software version for this device",
|
|
594
594
|
)
|
|
595
|
-
|
|
596
|
-
to="dcim.
|
|
595
|
+
controller_managed_device_group = models.ForeignKey(
|
|
596
|
+
to="dcim.ControllerManagedDeviceGroup",
|
|
597
597
|
on_delete=models.SET_NULL,
|
|
598
598
|
related_name="devices",
|
|
599
599
|
blank=True,
|
|
@@ -1247,7 +1247,7 @@ class SoftwareVersion(PrimaryModel):
|
|
|
1247
1247
|
"statuses",
|
|
1248
1248
|
"webhooks",
|
|
1249
1249
|
)
|
|
1250
|
-
class Controller(PrimaryModel
|
|
1250
|
+
class Controller(PrimaryModel):
|
|
1251
1251
|
"""Represents an entity that manages or controls one or more devices, acting as a central point of control.
|
|
1252
1252
|
|
|
1253
1253
|
A Controller can be deployed to a single device or a group of devices represented by a DeviceRedundancyGroup.
|
|
@@ -1278,21 +1278,21 @@ class Controller(PrimaryModel, ConfigContextModel):
|
|
|
1278
1278
|
)
|
|
1279
1279
|
external_integration = models.ForeignKey(
|
|
1280
1280
|
to="extras.ExternalIntegration",
|
|
1281
|
-
on_delete=models.
|
|
1281
|
+
on_delete=models.PROTECT,
|
|
1282
1282
|
related_name="controllers",
|
|
1283
1283
|
blank=True,
|
|
1284
1284
|
null=True,
|
|
1285
1285
|
)
|
|
1286
|
-
|
|
1286
|
+
controller_device = models.ForeignKey(
|
|
1287
1287
|
to="dcim.Device",
|
|
1288
|
-
on_delete=models.
|
|
1288
|
+
on_delete=models.PROTECT,
|
|
1289
1289
|
related_name="controllers",
|
|
1290
1290
|
blank=True,
|
|
1291
1291
|
null=True,
|
|
1292
1292
|
)
|
|
1293
|
-
|
|
1293
|
+
controller_device_redundancy_group = models.ForeignKey(
|
|
1294
1294
|
to="dcim.DeviceRedundancyGroup",
|
|
1295
|
-
on_delete=models.
|
|
1295
|
+
on_delete=models.PROTECT,
|
|
1296
1296
|
related_name="controllers",
|
|
1297
1297
|
blank=True,
|
|
1298
1298
|
null=True,
|
|
@@ -1307,12 +1307,10 @@ class Controller(PrimaryModel, ConfigContextModel):
|
|
|
1307
1307
|
def clean(self):
|
|
1308
1308
|
super().clean()
|
|
1309
1309
|
|
|
1310
|
-
if self.
|
|
1310
|
+
if self.controller_device and self.controller_device_redundancy_group:
|
|
1311
1311
|
raise ValidationError(
|
|
1312
1312
|
{
|
|
1313
|
-
"
|
|
1314
|
-
"Cannot assign both a device and a device redundancy group to a controller."
|
|
1315
|
-
),
|
|
1313
|
+
"controller_device": ("Cannot assign both a device and a device redundancy group to a controller."),
|
|
1316
1314
|
},
|
|
1317
1315
|
)
|
|
1318
1316
|
|
|
@@ -1331,7 +1329,7 @@ class Controller(PrimaryModel, ConfigContextModel):
|
|
|
1331
1329
|
"graphql",
|
|
1332
1330
|
"webhooks",
|
|
1333
1331
|
)
|
|
1334
|
-
class
|
|
1332
|
+
class ControllerManagedDeviceGroup(TreeModel, PrimaryModel):
|
|
1335
1333
|
"""Represents a mapping of controlled devices to a specific controller.
|
|
1336
1334
|
|
|
1337
1335
|
This model allows for the organization of controlled devices into hierarchical groups for structured representation.
|
|
@@ -1349,7 +1347,7 @@ class ControllerDeviceGroup(TreeModel, PrimaryModel, ConfigContextModel):
|
|
|
1349
1347
|
controller = models.ForeignKey(
|
|
1350
1348
|
to="dcim.Controller",
|
|
1351
1349
|
on_delete=models.CASCADE,
|
|
1352
|
-
related_name="
|
|
1350
|
+
related_name="controller_managed_device_groups",
|
|
1353
1351
|
blank=False,
|
|
1354
1352
|
null=False,
|
|
1355
1353
|
help_text="Controller that manages the devices in this group",
|
|
@@ -1373,7 +1371,7 @@ class ControllerDeviceGroup(TreeModel, PrimaryModel, ConfigContextModel):
|
|
|
1373
1371
|
if self.controller == self._original_controller and self.parent == self._original_parent:
|
|
1374
1372
|
return
|
|
1375
1373
|
|
|
1376
|
-
if self.parent and self.controller and self.
|
|
1374
|
+
if self.parent and self.controller and self.controller != self.parent.controller:
|
|
1377
1375
|
raise ValidationError(
|
|
1378
1376
|
{"controller": "Controller device group must have the same controller as the parent group."}
|
|
1379
1377
|
)
|
nautobot/dcim/models/racks.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from collections import OrderedDict
|
|
2
|
-
|
|
3
1
|
from django.conf import settings
|
|
4
2
|
from django.contrib.contenttypes.fields import GenericRelation
|
|
5
3
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -275,7 +273,7 @@ class Rack(PrimaryModel):
|
|
|
275
273
|
contains a height attribute for the device
|
|
276
274
|
"""
|
|
277
275
|
|
|
278
|
-
elevation =
|
|
276
|
+
elevation = {}
|
|
279
277
|
for u in self.units:
|
|
280
278
|
elevation[u] = {
|
|
281
279
|
"id": u,
|
nautobot/dcim/navigation.py
CHANGED
|
@@ -18,33 +18,33 @@ menu_items = (
|
|
|
18
18
|
weight=150,
|
|
19
19
|
items=(
|
|
20
20
|
NavMenuItem(
|
|
21
|
-
link="dcim:
|
|
22
|
-
name="
|
|
21
|
+
link="dcim:location_list",
|
|
22
|
+
name="Locations",
|
|
23
23
|
weight=100,
|
|
24
24
|
permissions=[
|
|
25
|
-
"dcim.
|
|
25
|
+
"dcim.view_location",
|
|
26
26
|
],
|
|
27
27
|
buttons=(
|
|
28
28
|
NavMenuAddButton(
|
|
29
|
-
link="dcim:
|
|
29
|
+
link="dcim:location_add",
|
|
30
30
|
permissions=[
|
|
31
|
-
"dcim.
|
|
31
|
+
"dcim.add_location",
|
|
32
32
|
],
|
|
33
33
|
),
|
|
34
34
|
),
|
|
35
35
|
),
|
|
36
36
|
NavMenuItem(
|
|
37
|
-
link="dcim:
|
|
38
|
-
name="
|
|
37
|
+
link="dcim:locationtype_list",
|
|
38
|
+
name="Location Types",
|
|
39
39
|
weight=200,
|
|
40
40
|
permissions=[
|
|
41
|
-
"dcim.
|
|
41
|
+
"dcim.view_locationtype",
|
|
42
42
|
],
|
|
43
43
|
buttons=(
|
|
44
44
|
NavMenuAddButton(
|
|
45
|
-
link="dcim:
|
|
45
|
+
link="dcim:locationtype_add",
|
|
46
46
|
permissions=[
|
|
47
|
-
"dcim.
|
|
47
|
+
"dcim.add_locationtype",
|
|
48
48
|
],
|
|
49
49
|
),
|
|
50
50
|
),
|
|
@@ -211,33 +211,33 @@ menu_items = (
|
|
|
211
211
|
),
|
|
212
212
|
),
|
|
213
213
|
NavMenuItem(
|
|
214
|
-
link="dcim:
|
|
215
|
-
name="
|
|
214
|
+
link="dcim:devicefamily_list",
|
|
215
|
+
name="Device Families",
|
|
216
216
|
weight=200,
|
|
217
217
|
permissions=[
|
|
218
|
-
"dcim.
|
|
218
|
+
"dcim.view_devicefamily",
|
|
219
219
|
],
|
|
220
220
|
buttons=(
|
|
221
221
|
NavMenuAddButton(
|
|
222
|
-
link="dcim:
|
|
222
|
+
link="dcim:devicefamily_add",
|
|
223
223
|
permissions=[
|
|
224
|
-
"dcim.
|
|
224
|
+
"dcim.add_devicefamily",
|
|
225
225
|
],
|
|
226
226
|
),
|
|
227
227
|
),
|
|
228
228
|
),
|
|
229
229
|
NavMenuItem(
|
|
230
|
-
link="dcim:
|
|
231
|
-
name="
|
|
230
|
+
link="dcim:manufacturer_list",
|
|
231
|
+
name="Manufacturers",
|
|
232
232
|
weight=300,
|
|
233
233
|
permissions=[
|
|
234
|
-
"dcim.
|
|
234
|
+
"dcim.view_manufacturer",
|
|
235
235
|
],
|
|
236
236
|
buttons=(
|
|
237
237
|
NavMenuAddButton(
|
|
238
|
-
link="dcim:
|
|
238
|
+
link="dcim:manufacturer_add",
|
|
239
239
|
permissions=[
|
|
240
|
-
"dcim.
|
|
240
|
+
"dcim.add_manufacturer",
|
|
241
241
|
],
|
|
242
242
|
),
|
|
243
243
|
),
|
|
@@ -319,20 +319,12 @@ menu_items = (
|
|
|
319
319
|
),
|
|
320
320
|
),
|
|
321
321
|
NavMenuItem(
|
|
322
|
-
link="dcim:
|
|
323
|
-
name="
|
|
322
|
+
link="dcim:controllermanageddevicegroup_list",
|
|
323
|
+
name="Managed Device Groups",
|
|
324
324
|
weight=200,
|
|
325
325
|
permissions=[
|
|
326
|
-
"dcim.
|
|
326
|
+
"dcim.view_controllermanageddevicegroup",
|
|
327
327
|
],
|
|
328
|
-
buttons=(
|
|
329
|
-
NavMenuAddButton(
|
|
330
|
-
link="dcim:controllerdevicegroup_add",
|
|
331
|
-
permissions=[
|
|
332
|
-
"dcim.add_controllerdevicegroup",
|
|
333
|
-
],
|
|
334
|
-
),
|
|
335
|
-
),
|
|
336
328
|
),
|
|
337
329
|
),
|
|
338
330
|
),
|
nautobot/dcim/signals.py
CHANGED
|
@@ -11,7 +11,7 @@ from nautobot.core.signals import disable_for_loaddata
|
|
|
11
11
|
from .models import (
|
|
12
12
|
Cable,
|
|
13
13
|
CablePath,
|
|
14
|
-
|
|
14
|
+
ControllerManagedDeviceGroup,
|
|
15
15
|
Device,
|
|
16
16
|
DeviceRedundancyGroup,
|
|
17
17
|
Interface,
|
|
@@ -290,17 +290,17 @@ def prevent_adding_tagged_vlans_with_incorrect_mode_or_site(sender, instance, ac
|
|
|
290
290
|
|
|
291
291
|
|
|
292
292
|
#
|
|
293
|
-
#
|
|
293
|
+
# ControllerManagedDeviceGroup
|
|
294
294
|
#
|
|
295
295
|
|
|
296
296
|
|
|
297
|
-
@receiver(post_save, sender=
|
|
298
|
-
def
|
|
299
|
-
"""Update descendants when the top level `
|
|
297
|
+
@receiver(post_save, sender=ControllerManagedDeviceGroup)
|
|
298
|
+
def handle_controller_managed_device_group_controller_change(instance, raw=False, **_):
|
|
299
|
+
"""Update descendants when the top level `ControllerManagedDeviceGroup.controller` changes."""
|
|
300
300
|
if instance.parent or instance._original_controller == instance.controller:
|
|
301
301
|
return
|
|
302
302
|
|
|
303
|
-
logger = logging.getLogger(__name__ + ".
|
|
303
|
+
logger = logging.getLogger(__name__ + ".ControllerManagedDeviceGroup")
|
|
304
304
|
|
|
305
305
|
if raw:
|
|
306
306
|
logger.debug("Skipping controller update for imported controller device group %s", instance)
|