nautobot 2.2.0b1__py3-none-any.whl → 2.2.2__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 +45 -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 +24 -10
- nautobot/core/views/mixins.py +11 -4
- nautobot/core/views/renderers.py +11 -6
- nautobot/core/wsgi.py +9 -2
- 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 +69 -48
- 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 +74 -42
- 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_forms.py +49 -2
- 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 +71 -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 +95 -51
- nautobot/extras/tasks.py +70 -17
- nautobot/extras/tests/test_api.py +2 -4
- nautobot/extras/tests/test_context_managers.py +98 -1
- 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/utils.py +37 -0
- 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 +3 -23
- 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 +663 -304
- 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 +711 -125
- 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.2.dist-info}/METADATA +24 -24
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/RECORD +422 -416
- 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.2.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/NOTICE +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/WHEEL +0 -0
- {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/entry_points.txt +0 -0
|
@@ -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/utils.py
CHANGED
|
@@ -19,7 +19,9 @@ from nautobot.core.choices import ColorChoices
|
|
|
19
19
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
20
20
|
from nautobot.core.models.managers import TagsManager
|
|
21
21
|
from nautobot.core.models.utils import find_models_with_matching_fields
|
|
22
|
+
from nautobot.extras.choices import ObjectChangeActionChoices
|
|
22
23
|
from nautobot.extras.constants import (
|
|
24
|
+
CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL,
|
|
23
25
|
EXTRAS_FEATURES,
|
|
24
26
|
JOB_MAX_NAME_LENGTH,
|
|
25
27
|
JOB_OVERRIDABLE_FIELDS,
|
|
@@ -610,3 +612,38 @@ def migrate_role_data(
|
|
|
610
612
|
model_to_migrate._meta.label,
|
|
611
613
|
to_role_field_name,
|
|
612
614
|
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def bulk_delete_with_bulk_change_logging(qs, batch_size=1000):
|
|
618
|
+
"""
|
|
619
|
+
Deletes objects in the provided queryset and creates ObjectChange instances in bulk to improve performance.
|
|
620
|
+
For use with bulk delete views. This operation is wrapped in an atomic transaction.
|
|
621
|
+
"""
|
|
622
|
+
from nautobot.extras.models import ObjectChange
|
|
623
|
+
from nautobot.extras.signals import change_context_state
|
|
624
|
+
|
|
625
|
+
change_context = change_context_state.get()
|
|
626
|
+
if change_context is None:
|
|
627
|
+
raise ValueError("Change logging must be enabled before using bulk_delete_with_bulk_change_logging")
|
|
628
|
+
|
|
629
|
+
with transaction.atomic():
|
|
630
|
+
try:
|
|
631
|
+
queued_object_changes = []
|
|
632
|
+
change_context.defer_object_changes = True
|
|
633
|
+
for obj in qs.iterator():
|
|
634
|
+
if not hasattr(obj, "to_objectchange"):
|
|
635
|
+
break
|
|
636
|
+
if len(queued_object_changes) >= batch_size:
|
|
637
|
+
ObjectChange.objects.bulk_create(queued_object_changes)
|
|
638
|
+
queued_object_changes = []
|
|
639
|
+
oc = obj.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
|
|
640
|
+
oc.user = change_context.user
|
|
641
|
+
oc.request_id = change_context.change_id
|
|
642
|
+
oc.change_context = change_context.context
|
|
643
|
+
oc.change_context_detail = change_context.context_detail[:CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL]
|
|
644
|
+
queued_object_changes.append(oc)
|
|
645
|
+
ObjectChange.objects.bulk_create(queued_object_changes)
|
|
646
|
+
return qs.delete()
|
|
647
|
+
finally:
|
|
648
|
+
change_context.defer_object_changes = False
|
|
649
|
+
change_context.reset_deferred_object_changes()
|
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)
|
nautobot/ipam/api/views.py
CHANGED
|
@@ -6,7 +6,6 @@ from rest_framework import status
|
|
|
6
6
|
from rest_framework.decorators import action
|
|
7
7
|
from rest_framework.exceptions import APIException
|
|
8
8
|
from rest_framework.response import Response
|
|
9
|
-
from rest_framework.routers import APIRootView
|
|
10
9
|
|
|
11
10
|
from nautobot.core.models.querysets import count_related
|
|
12
11
|
from nautobot.core.utils.config import get_settings_or_config
|
|
@@ -32,16 +31,6 @@ from nautobot.ipam.models import (
|
|
|
32
31
|
|
|
33
32
|
from . import serializers
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
class IPAMRootView(APIRootView):
|
|
37
|
-
"""
|
|
38
|
-
IPAM API root view
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
def get_view_name(self):
|
|
42
|
-
return "IPAM"
|
|
43
|
-
|
|
44
|
-
|
|
45
34
|
#
|
|
46
35
|
# Namespace
|
|
47
36
|
#
|
nautobot/ipam/apps.py
CHANGED
nautobot/ipam/tables.py
CHANGED
|
@@ -42,28 +42,10 @@ UTILIZATION_GRAPH = """
|
|
|
42
42
|
{% if record.present_in_database %}{% utilization_graph record.get_utilization %}{% else %}—{% endif %}
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
PREFIX_LINK = """
|
|
46
|
-
{% load helpers %}
|
|
47
|
-
{% for i in record.ancestors.count|as_range %}
|
|
48
|
-
<i class="mdi mdi-circle-small"></i>
|
|
49
|
-
{% endfor %}
|
|
50
|
-
<a href="\
|
|
51
|
-
{% if record.present_in_database %}\
|
|
52
|
-
{% url 'ipam:prefix' pk=record.pk %}\
|
|
53
|
-
{% else %}\
|
|
54
|
-
{% url 'ipam:prefix_add' %}\
|
|
55
|
-
?prefix={{ record }}&namespace={{ object.namespace.pk }}\
|
|
56
|
-
{% for loc in object.locations.all %}&locations={{ loc.pk }}{% endfor %}\
|
|
57
|
-
{% if object.tenant %}&tenant_group={{ object.tenant.tenant_group.pk }}&tenant={{ object.tenant.pk }}{% endif %}\
|
|
58
|
-
{% endif %}\
|
|
59
|
-
">{{ record.prefix }}</a>
|
|
60
|
-
"""
|
|
61
45
|
|
|
62
46
|
PREFIX_COPY_LINK = """
|
|
63
47
|
{% load helpers %}
|
|
64
|
-
{%
|
|
65
|
-
<i class="mdi mdi-circle-small"></i>
|
|
66
|
-
{% endfor %}
|
|
48
|
+
{% tree_hierarchy_ui_representation record.ancestors.count|as_range table.hide_hierarchy_ui%}
|
|
67
49
|
<span class="hover_copy">
|
|
68
50
|
<a href="\
|
|
69
51
|
{% if record.present_in_database %}\
|
|
@@ -179,7 +161,6 @@ VLAN_LINK = """
|
|
|
179
161
|
{% url 'ipam:vlan_add' %}\
|
|
180
162
|
?vid={{ record.vid }}&vlan_group={{ vlan_group.pk }}\
|
|
181
163
|
{% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
|
|
182
|
-
{% if vlan_group.location %}&location={{ vlan_group.location.pk }}{% endif %}\
|
|
183
164
|
" class="btn btn-xs btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>\
|
|
184
165
|
{% else %}
|
|
185
166
|
{{ record.available }} VLAN{{ record.available|pluralize }} available
|
|
@@ -369,7 +350,7 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
369
350
|
namespace = tables.Column(linkify=True)
|
|
370
351
|
vlan = tables.Column(linkify=True, verbose_name="VLAN")
|
|
371
352
|
rir = tables.Column(linkify=True)
|
|
372
|
-
children = tables.Column(accessor="descendants_count")
|
|
353
|
+
children = tables.Column(accessor="descendants_count", orderable=False)
|
|
373
354
|
date_allocated = tables.DateTimeColumn()
|
|
374
355
|
location_count = LinkedCountColumn(
|
|
375
356
|
viewname="dcim:location_list", url_params={"prefixes": "pk"}, verbose_name="Locations"
|
|
@@ -377,7 +358,6 @@ class PrefixTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
|
377
358
|
|
|
378
359
|
class Meta(BaseTable.Meta):
|
|
379
360
|
model = Prefix
|
|
380
|
-
orderable = False
|
|
381
361
|
fields = (
|
|
382
362
|
"pk",
|
|
383
363
|
"prefix",
|
|
@@ -791,7 +771,7 @@ class InterfaceVLANTable(StatusTableMixin, BaseTable):
|
|
|
791
771
|
tenant = TenantColumn()
|
|
792
772
|
role = tables.TemplateColumn(template_code=VLAN_ROLE_LINK)
|
|
793
773
|
location_count = LinkedCountColumn(
|
|
794
|
-
viewname="dcim:
|
|
774
|
+
viewname="dcim:location_list",
|
|
795
775
|
url_params={"vlans": "pk"},
|
|
796
776
|
verbose_name="Locations",
|
|
797
777
|
)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from django.test import override_settings
|
|
2
1
|
from django.urls import reverse
|
|
3
2
|
from rest_framework import status
|
|
4
3
|
|
|
@@ -9,8 +8,8 @@ class TestPrefix(APITestCase):
|
|
|
9
8
|
def setUp(self):
|
|
10
9
|
super().setUp()
|
|
11
10
|
self.api_url = reverse("graphql-api")
|
|
11
|
+
self.add_permissions("ipam.view_prefix")
|
|
12
12
|
|
|
13
|
-
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
14
13
|
def test_prefix_ip_version(self):
|
|
15
14
|
"""Test ip_version is available for a Prefix via GraphQL."""
|
|
16
15
|
get_prefixes_query = """
|
|
@@ -23,7 +22,7 @@ class TestPrefix(APITestCase):
|
|
|
23
22
|
}
|
|
24
23
|
"""
|
|
25
24
|
payload = {"query": get_prefixes_query}
|
|
26
|
-
response = self.client.post(self.api_url, payload, format="json")
|
|
25
|
+
response = self.client.post(self.api_url, payload, format="json", **self.header)
|
|
27
26
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
28
27
|
prefixes = response.data["data"]["prefixes"]
|
|
29
28
|
self.assertIsInstance(prefixes, list)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
from nautobot.core.models.querysets import count_related
|
|
4
|
+
from nautobot.dcim.models.locations import Location
|
|
5
|
+
from nautobot.ipam.models import Prefix
|
|
6
|
+
from nautobot.ipam.tables import PrefixTable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PrefixTableTestCase(TestCase):
|
|
10
|
+
def _validate_sorted_queryset_same_with_table_queryset(self, queryset, table_class, field_name):
|
|
11
|
+
with self.subTest(f"Assert sorting {table_class.__name__} on '{field_name}'"):
|
|
12
|
+
table = table_class(queryset, order_by=field_name)
|
|
13
|
+
table_queryset_data = table.data.data.values_list("pk", flat=True)
|
|
14
|
+
sorted_queryset = queryset.order_by(field_name).values_list("pk", flat=True)
|
|
15
|
+
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
16
|
+
|
|
17
|
+
def test_prefix_table_sort(self):
|
|
18
|
+
"""Assert TreeNode model table are orderable."""
|
|
19
|
+
# Due to MySQL's lack of support for combining 'LIMIT' and 'ORDER BY' in a single query,
|
|
20
|
+
# hence this approach.
|
|
21
|
+
pk_list = Prefix.objects.all().values_list("pk", flat=True)[:20]
|
|
22
|
+
pk_list = [str(pk) for pk in pk_list]
|
|
23
|
+
queryset = Prefix.objects.filter(pk__in=pk_list)
|
|
24
|
+
|
|
25
|
+
# Assets model names
|
|
26
|
+
table_avail_fields = ["tenant", "vlan", "namespace"]
|
|
27
|
+
for table_field_name in table_avail_fields:
|
|
28
|
+
self._validate_sorted_queryset_same_with_table_queryset(queryset, PrefixTable, table_field_name)
|
|
29
|
+
self._validate_sorted_queryset_same_with_table_queryset(queryset, PrefixTable, f"-{table_field_name}")
|
|
30
|
+
|
|
31
|
+
# Assert `prefix`
|
|
32
|
+
table_queryset_data = PrefixTable(queryset, order_by="prefix").data.data.values_list("pk", flat=True)
|
|
33
|
+
prefix_queryset = queryset.order_by("network", "prefix_length").values_list("pk", flat=True)
|
|
34
|
+
self.assertEqual(list(table_queryset_data), list(prefix_queryset))
|
|
35
|
+
table_queryset_data = PrefixTable(queryset, order_by="-prefix").data.data.values_list("pk", flat=True)
|
|
36
|
+
prefix_queryset = queryset.order_by("-network", "-prefix_length").values_list("pk", flat=True)
|
|
37
|
+
self.assertEqual(list(table_queryset_data), list(prefix_queryset))
|
|
38
|
+
|
|
39
|
+
# Assets `location_count`
|
|
40
|
+
location_count_queryset = queryset.annotate(location_count=count_related(Location, "prefixes")).all()
|
|
41
|
+
self._validate_sorted_queryset_same_with_table_queryset(location_count_queryset, PrefixTable, "location_count")
|
|
42
|
+
self._validate_sorted_queryset_same_with_table_queryset(location_count_queryset, PrefixTable, "-location_count")
|
|
@@ -126,6 +126,7 @@ class RIRTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
|
|
|
126
126
|
|
|
127
127
|
class PrefixTestCase(ViewTestCases.PrimaryObjectViewTestCase, ViewTestCases.ListObjectsViewTestCase):
|
|
128
128
|
model = Prefix
|
|
129
|
+
filter_on_field = "prefix_length"
|
|
129
130
|
|
|
130
131
|
@classmethod
|
|
131
132
|
def setUpTestData(cls):
|
nautobot/ipam/views.py
CHANGED
|
@@ -793,7 +793,7 @@ class IPAddressEditView(generic.ObjectEditView):
|
|
|
793
793
|
_, error_msg = retrieve_interface_or_vminterface_from_request(request)
|
|
794
794
|
if error_msg:
|
|
795
795
|
messages.warning(request, error_msg)
|
|
796
|
-
return redirect(self.get_return_url(request
|
|
796
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
797
797
|
|
|
798
798
|
return super().dispatch(request, *args, **kwargs)
|
|
799
799
|
|
|
@@ -876,17 +876,17 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
|
|
|
876
876
|
"""
|
|
877
877
|
|
|
878
878
|
queryset = IPAddress.objects.all()
|
|
879
|
-
default_return_url = "ipam:ipaddress_add"
|
|
880
879
|
|
|
881
880
|
def dispatch(self, request, *args, **kwargs):
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
881
|
+
if request.user.is_authenticated:
|
|
882
|
+
# Redirect user if an interface has not been provided
|
|
883
|
+
if "interface" not in request.GET and "vminterface" not in request.GET:
|
|
884
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
885
885
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
886
|
+
_, error_msg = retrieve_interface_or_vminterface_from_request(request)
|
|
887
|
+
if error_msg:
|
|
888
|
+
messages.warning(request, error_msg)
|
|
889
|
+
return redirect(self.get_return_url(request, default_return_url="ipam:ipaddress_add"))
|
|
890
890
|
|
|
891
891
|
return super().dispatch(request, *args, **kwargs)
|
|
892
892
|
|