nautobot 2.4.9__py3-none-any.whl → 2.4.11__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/cloud/tests/test_views.py +13 -1
- nautobot/cloud/views.py +39 -9
- nautobot/core/api/parsers.py +56 -2
- nautobot/core/celery/__init__.py +21 -0
- nautobot/core/celery/encoders.py +3 -0
- nautobot/core/forms/forms.py +4 -1
- nautobot/core/jobs/bulk_actions.py +8 -8
- nautobot/core/jobs/cleanup.py +11 -0
- nautobot/core/management/commands/generate_test_data.py +2 -1
- nautobot/core/models/__init__.py +2 -0
- nautobot/core/templates/generic/object_retrieve.html +1 -1
- nautobot/core/testing/mixins.py +19 -1
- nautobot/core/testing/views.py +104 -8
- nautobot/core/tests/test_csv.py +92 -1
- nautobot/core/tests/test_jinja_filters.py +59 -0
- nautobot/core/tests/test_jobs.py +20 -4
- nautobot/core/tests/test_utils.py +193 -0
- nautobot/core/tests/test_views.py +73 -0
- nautobot/core/tests/test_views_utils.py +53 -2
- nautobot/core/ui/object_detail.py +4 -0
- nautobot/core/urls.py +2 -2
- nautobot/core/utils/lookup.py +4 -2
- nautobot/core/utils/module_loading.py +86 -58
- nautobot/core/views/__init__.py +21 -0
- nautobot/core/views/generic.py +2 -12
- nautobot/core/views/mixins.py +19 -1
- nautobot/core/views/renderers.py +4 -13
- nautobot/core/views/utils.py +16 -0
- nautobot/dcim/api/serializers.py +13 -0
- nautobot/dcim/api/urls.py +1 -0
- nautobot/dcim/api/views.py +20 -0
- nautobot/dcim/apps.py +1 -0
- nautobot/dcim/factory.py +11 -0
- nautobot/dcim/filters/__init__.py +110 -0
- nautobot/dcim/forms.py +205 -19
- nautobot/dcim/migrations/0070_modulefamily_models.py +92 -0
- nautobot/dcim/models/__init__.py +2 -0
- nautobot/dcim/models/device_component_templates.py +18 -0
- nautobot/dcim/models/device_components.py +25 -1
- nautobot/dcim/models/devices.py +68 -0
- nautobot/dcim/navigation.py +16 -0
- nautobot/dcim/tables/__init__.py +2 -0
- nautobot/dcim/tables/devices.py +48 -0
- nautobot/dcim/tables/devicetypes.py +35 -1
- nautobot/dcim/tables/template_code.py +2 -0
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -90
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +1 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +1 -63
- nautobot/dcim/templates/dcim/location.html +2 -249
- nautobot/dcim/templates/dcim/location_edit.html +2 -38
- nautobot/dcim/templates/dcim/location_retrieve.html +249 -0
- nautobot/dcim/templates/dcim/location_update.html +38 -0
- nautobot/dcim/templates/dcim/module_update.html +1 -0
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +93 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +31 -0
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +6 -0
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -160
- nautobot/dcim/tests/test_api.py +35 -0
- nautobot/dcim/tests/test_filters.py +102 -3
- nautobot/dcim/tests/test_models.py +146 -0
- nautobot/dcim/tests/test_views.py +70 -97
- nautobot/dcim/urls.py +4 -22
- nautobot/dcim/views.py +439 -153
- nautobot/extras/api/views.py +9 -2
- nautobot/extras/context_managers.py +2 -2
- nautobot/extras/datasources/git.py +11 -3
- nautobot/extras/forms/forms.py +9 -5
- nautobot/extras/jobs.py +4 -2
- nautobot/extras/models/customfields.py +2 -0
- nautobot/extras/models/datasources.py +13 -8
- nautobot/extras/models/groups.py +18 -0
- nautobot/extras/models/jobs.py +19 -0
- nautobot/extras/models/metadata.py +2 -0
- nautobot/extras/models/models.py +4 -0
- nautobot/extras/models/secrets.py +7 -0
- nautobot/extras/plugins/__init__.py +3 -0
- nautobot/extras/secrets/__init__.py +14 -0
- nautobot/extras/tables.py +40 -3
- nautobot/extras/templates/extras/configcontext.html +2 -220
- nautobot/extras/templates/extras/configcontext_edit.html +2 -50
- nautobot/extras/templates/extras/configcontext_retrieve.html +2 -0
- nautobot/extras/templates/extras/configcontext_update.html +50 -0
- nautobot/extras/templates/extras/configcontextschema.html +2 -48
- nautobot/extras/templates/extras/configcontextschema_edit.html +2 -19
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +48 -0
- nautobot/extras/templates/extras/configcontextschema_update.html +19 -0
- nautobot/extras/templates/extras/inc/configcontext_data.html +1 -0
- nautobot/extras/templates/extras/inc/json_data.html +1 -1
- nautobot/extras/templates/extras/inc/json_format.html +2 -2
- nautobot/extras/templates/extras/job_edit.html +12 -6
- nautobot/extras/templates/extras/tag.html +2 -52
- nautobot/extras/templates/extras/tag_edit.html +2 -15
- nautobot/extras/templates/extras/tag_retrieve.html +52 -0
- nautobot/extras/templates/extras/tag_update.html +15 -0
- nautobot/extras/templates/extras/team_retrieve.html +2 -2
- nautobot/extras/tests/test_api.py +15 -15
- nautobot/extras/tests/test_context_managers.py +20 -0
- nautobot/extras/tests/test_filters.py +4 -4
- nautobot/extras/tests/test_jobs.py +23 -10
- nautobot/extras/tests/test_models.py +45 -8
- nautobot/extras/tests/test_plugins.py +6 -3
- nautobot/extras/tests/test_views.py +66 -11
- nautobot/extras/urls.py +4 -134
- nautobot/extras/views.py +113 -158
- nautobot/ipam/models.py +51 -4
- nautobot/ipam/tables.py +19 -0
- nautobot/ipam/templates/ipam/vlan.html +2 -84
- nautobot/ipam/templates/ipam/vlan_edit.html +2 -24
- nautobot/ipam/templates/ipam/vlan_retrieve.html +84 -0
- nautobot/ipam/templates/ipam/vlan_update.html +24 -0
- nautobot/ipam/tests/test_views.py +5 -0
- nautobot/ipam/urls.py +1 -21
- nautobot/ipam/views.py +45 -70
- nautobot/project-static/docs/404.html +31 -8
- nautobot/project-static/docs/apps/index.html +31 -8
- nautobot/project-static/docs/apps/nautobot-apps.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +120 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +31 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +31 -8
- nautobot/project-static/docs/development/apps/api/configuration-view.html +31 -8
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/global-search.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/graphql.html +31 -8
- nautobot/project-static/docs/development/apps/api/models/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +40 -8
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +70 -46
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +31 -8
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +31 -8
- nautobot/project-static/docs/development/apps/api/prometheus.html +31 -8
- nautobot/project-static/docs/development/apps/api/setup.html +31 -8
- nautobot/project-static/docs/development/apps/api/testing.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +31 -8
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/base-template.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/index.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/notes.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +31 -8
- nautobot/project-static/docs/development/apps/api/views/urls.html +31 -8
- nautobot/project-static/docs/development/apps/index.html +31 -8
- nautobot/project-static/docs/development/apps/migration/code-updates.html +31 -8
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +31 -8
- nautobot/project-static/docs/development/apps/migration/from-v1.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +31 -8
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +31 -8
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +31 -8
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +31 -8
- nautobot/project-static/docs/development/core/application-registry.html +31 -8
- nautobot/project-static/docs/development/core/best-practices.html +31 -8
- nautobot/project-static/docs/development/core/bootstrap-ui.html +31 -8
- nautobot/project-static/docs/development/core/caching.html +31 -8
- nautobot/project-static/docs/development/core/controllers.html +31 -8
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +31 -8
- nautobot/project-static/docs/development/core/generic-views.html +31 -8
- nautobot/project-static/docs/development/core/getting-started.html +31 -8
- nautobot/project-static/docs/development/core/homepage.html +31 -8
- nautobot/project-static/docs/development/core/index.html +31 -8
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +31 -8
- nautobot/project-static/docs/development/core/model-checklist.html +31 -8
- nautobot/project-static/docs/development/core/model-features.html +31 -8
- nautobot/project-static/docs/development/core/natural-keys.html +31 -8
- nautobot/project-static/docs/development/core/navigation-menu.html +31 -8
- nautobot/project-static/docs/development/core/release-checklist.html +31 -8
- nautobot/project-static/docs/development/core/role-internals.html +31 -8
- nautobot/project-static/docs/development/core/settings.html +31 -8
- nautobot/project-static/docs/development/core/style-guide.html +31 -8
- nautobot/project-static/docs/development/core/templates.html +31 -8
- nautobot/project-static/docs/development/core/testing.html +31 -8
- nautobot/project-static/docs/development/core/ui-component-framework.html +31 -8
- nautobot/project-static/docs/development/core/user-preferences.html +31 -8
- nautobot/project-static/docs/development/index.html +31 -8
- nautobot/project-static/docs/development/jobs/getting-started.html +35 -8
- nautobot/project-static/docs/development/jobs/index.html +31 -8
- nautobot/project-static/docs/development/jobs/installation.html +31 -8
- nautobot/project-static/docs/development/jobs/job-extensions.html +31 -8
- nautobot/project-static/docs/development/jobs/job-logging.html +31 -8
- nautobot/project-static/docs/development/jobs/job-patterns.html +31 -8
- nautobot/project-static/docs/development/jobs/job-structure.html +31 -8
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +31 -8
- nautobot/project-static/docs/development/jobs/testing.html +31 -8
- nautobot/project-static/docs/index.html +31 -8
- nautobot/project-static/docs/insert-analytics.sh +36 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +31 -8
- nautobot/project-static/docs/overview/design_philosophy.html +31 -8
- nautobot/project-static/docs/release-notes/index.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.0.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.1.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.2.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.3.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.4.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.5.html +31 -8
- nautobot/project-static/docs/release-notes/version-1.6.html +328 -8
- nautobot/project-static/docs/release-notes/version-2.0.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.1.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.2.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.3.html +31 -8
- nautobot/project-static/docs/release-notes/version-2.4.html +353 -8
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +302 -298
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +31 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +31 -8
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +31 -8
- nautobot/project-static/docs/user-guide/administration/installation/services.html +31 -8
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +31 -8
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +31 -8
- nautobot/project-static/docs/user-guide/administration/security/index.html +31 -9
- nautobot/project-static/docs/user-guide/administration/security/notices.html +144 -9
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +31 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +31 -8
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +43 -20
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +35 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +10261 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +34 -11
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +31 -8
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +41 -15
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +31 -8
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +31 -8
- nautobot/project-static/docs/user-guide/index.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +37 -9
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +31 -8
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +31 -8
- nautobot/tenancy/tables.py +2 -0
- nautobot/users/models.py +4 -0
- nautobot/virtualization/models.py +4 -0
- nautobot/virtualization/tests/test_views.py +1 -1
- nautobot/wireless/forms.py +0 -1
- nautobot/wireless/models.py +1 -1
- nautobot/wireless/tables.py +7 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/METADATA +4 -4
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/RECORD +433 -418
- /nautobot/dcim/templates/dcim/{platform_edit.html → platform_create.html} +0 -0
- /nautobot/extras/test_jobs/{pass.py → pass_job.py} +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/NOTICE +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/WHEEL +0 -0
- {nautobot-2.4.9.dist-info → nautobot-2.4.11.dist-info}/entry_points.txt +0 -0
|
@@ -1,29 +1,13 @@
|
|
|
1
|
-
from
|
|
2
|
-
import
|
|
3
|
-
from
|
|
1
|
+
from importlib.machinery import FileFinder, SOURCE_SUFFIXES, SourceFileLoader
|
|
2
|
+
from importlib.util import module_from_spec
|
|
3
|
+
from keyword import iskeyword
|
|
4
4
|
import logging
|
|
5
|
-
import os
|
|
6
5
|
import pkgutil
|
|
7
6
|
import sys
|
|
8
7
|
|
|
9
8
|
logger = logging.getLogger(__name__)
|
|
10
9
|
|
|
11
10
|
|
|
12
|
-
@contextmanager
|
|
13
|
-
def _temporarily_add_to_sys_path(path):
|
|
14
|
-
"""
|
|
15
|
-
Allow loading of modules and packages from within the provided directory by temporarily modifying `sys.path`.
|
|
16
|
-
|
|
17
|
-
On exit, it restores the original `sys.path` value.
|
|
18
|
-
"""
|
|
19
|
-
old_sys_path = sys.path.copy()
|
|
20
|
-
sys.path.insert(0, path)
|
|
21
|
-
try:
|
|
22
|
-
yield
|
|
23
|
-
finally:
|
|
24
|
-
sys.path = old_sys_path
|
|
25
|
-
|
|
26
|
-
|
|
27
11
|
def clear_module_from_sys_modules(module_name):
|
|
28
12
|
"""
|
|
29
13
|
Remove the module and all its submodules from sys.modules.
|
|
@@ -33,7 +17,29 @@ def clear_module_from_sys_modules(module_name):
|
|
|
33
17
|
del sys.modules[name]
|
|
34
18
|
|
|
35
19
|
|
|
36
|
-
def
|
|
20
|
+
def check_name_safe_to_import_privately(name: str) -> tuple[bool, str]:
|
|
21
|
+
"""
|
|
22
|
+
Make sure the given package/module name is "safe" to import from the filesystem.
|
|
23
|
+
|
|
24
|
+
In other words, make sure it's:
|
|
25
|
+
- a valid Python identifier and not a reserved keyword
|
|
26
|
+
- not the name of an existing "real" Python package or builtin
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
(bool, str): Whether safe to load, and an explanatory string fragment for logging/exception messages.
|
|
30
|
+
"""
|
|
31
|
+
if not name.isidentifier():
|
|
32
|
+
return False, "not a valid identifier"
|
|
33
|
+
if iskeyword(name):
|
|
34
|
+
return False, "a reserved keyword"
|
|
35
|
+
if name in sys.builtin_module_names:
|
|
36
|
+
return False, "a Python builtin"
|
|
37
|
+
if any(module_info.name == name for module_info in pkgutil.iter_modules()):
|
|
38
|
+
return False, "the name of an installed Python package"
|
|
39
|
+
return True, "a valid and non-conflicting module name"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def import_modules_privately(path, module_path=None, module_prefix="", ignore_import_errors=True):
|
|
37
43
|
"""
|
|
38
44
|
Import modules from the filesystem without adding the path permanently to `sys.path`.
|
|
39
45
|
|
|
@@ -49,49 +55,71 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
|
49
55
|
ignore_import_errors (bool): Exceptions raised while importing modules will be caught and logged.
|
|
50
56
|
If this is set as False, they will then be re-raised to be handled by the caller of this function.
|
|
51
57
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
loaded_modules = []
|
|
59
|
+
# We formerly used pkgutil.walk_packages() here to handle submodule loading with a multi-entry module_path,
|
|
60
|
+
# but that has the downside (and risk!) of automatically importing all packages that it finds in the given path,
|
|
61
|
+
# whether or not we actually want to do so. So instead, for the case where a module_path is provided, we need to
|
|
62
|
+
# iteratively import each submodule ourselves.
|
|
63
|
+
if module_path:
|
|
64
|
+
# Git repository case - e.g. import_modules_privately(settings.GIT_ROOT, module_path=[repository_slug, "jobs"])
|
|
65
|
+
# Here we want to ONLY auto-load the module sequence identified by module_path.
|
|
66
|
+
permitted, reason = check_name_safe_to_import_privately(module_path[0])
|
|
67
|
+
if not permitted:
|
|
68
|
+
logger.error("Unable to load module %r from %s as it is %s", module_path[0], path, reason)
|
|
69
|
+
else:
|
|
70
|
+
module = None
|
|
71
|
+
module_name = module_path.pop(0)
|
|
72
|
+
submodule_name = module_name
|
|
73
|
+
try:
|
|
74
|
+
while True:
|
|
75
|
+
finder = FileFinder(path, (SourceFileLoader, SOURCE_SUFFIXES))
|
|
76
|
+
finder.invalidate_caches()
|
|
77
|
+
spec = finder.find_spec(module_name)
|
|
78
|
+
if spec is None or spec.loader is None:
|
|
79
|
+
logger.error("Unable to find module spec and/or loader for %r", submodule_name)
|
|
80
|
+
break
|
|
81
|
+
spec.name = submodule_name
|
|
82
|
+
spec.loader.name = submodule_name
|
|
83
|
+
submodule = module_from_spec(spec)
|
|
84
|
+
sys.modules[submodule_name] = submodule
|
|
85
|
+
spec.loader.exec_module(submodule)
|
|
86
|
+
if module is not None:
|
|
87
|
+
setattr(module, module_name, submodule)
|
|
88
|
+
module = submodule
|
|
89
|
+
loaded_modules.append(module)
|
|
90
|
+
if module_path:
|
|
91
|
+
submodule_name = f"{module_name}.{module_path[0]}"
|
|
92
|
+
module_name = module_path.pop(0)
|
|
93
|
+
path = module.__path__[0]
|
|
94
|
+
else:
|
|
95
|
+
break
|
|
96
|
+
except Exception as exc:
|
|
97
|
+
logger.error("Unable to load module %s from %s: %s", module_name, path, exc)
|
|
98
|
+
if not ignore_import_errors:
|
|
99
|
+
raise
|
|
55
100
|
else:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
for finder, discovered_module_name,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
or discovered_module_name.startswith(f"{module_prefix}.") # my_repo/jobs/foobar.py
|
|
63
|
-
):
|
|
101
|
+
# JOBS_ROOT case - import ALL top-level modules/packages that we can find in the given path;
|
|
102
|
+
# they can implement and import submodules as desired by themselves, but we only autoimport top-level ones.
|
|
103
|
+
for finder, discovered_module_name, _ in pkgutil.iter_modules([path]):
|
|
104
|
+
permitted, reason = check_name_safe_to_import_privately(discovered_module_name)
|
|
105
|
+
if not permitted:
|
|
106
|
+
logger.error("Unable to load module %r from %s as it is %s", discovered_module_name, path, reason)
|
|
64
107
|
continue
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
existing_module = None
|
|
69
|
-
if existing_module is not None:
|
|
70
|
-
existing_module_path = os.path.realpath(existing_module.origin)
|
|
71
|
-
if not existing_module_path.startswith(path):
|
|
72
|
-
logger.error(
|
|
73
|
-
"Unable to load module %s from %s as it conflicts with existing module %s",
|
|
74
|
-
discovered_module_name,
|
|
75
|
-
path,
|
|
76
|
-
existing_module_path,
|
|
77
|
-
)
|
|
78
|
-
continue
|
|
79
|
-
|
|
80
|
-
if discovered_module_name in sys.modules:
|
|
81
|
-
clear_module_from_sys_modules(discovered_module_name)
|
|
108
|
+
module_name = discovered_module_name
|
|
109
|
+
if module_name in sys.modules:
|
|
110
|
+
clear_module_from_sys_modules(module_name)
|
|
82
111
|
|
|
83
112
|
try:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
module = importlib.import_module(discovered_module_name)
|
|
93
|
-
importlib.reload(module)
|
|
113
|
+
spec = finder.find_spec(discovered_module_name)
|
|
114
|
+
if spec is None or spec.loader is None:
|
|
115
|
+
logger.error("Unable to find module spec and/or loader for %r", discovered_module_name)
|
|
116
|
+
continue
|
|
117
|
+
module = module_from_spec(spec)
|
|
118
|
+
sys.modules[module_name] = module
|
|
119
|
+
spec.loader.exec_module(module)
|
|
120
|
+
loaded_modules.append(module)
|
|
94
121
|
except Exception as exc:
|
|
95
122
|
logger.error("Unable to load module %s from %s: %s", discovered_module_name, path, exc)
|
|
96
123
|
if not ignore_import_errors:
|
|
97
124
|
raise
|
|
125
|
+
return loaded_modules
|
nautobot/core/views/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@ import datetime
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import platform
|
|
6
|
+
import posixpath
|
|
6
7
|
import re
|
|
7
8
|
import sys
|
|
8
9
|
import time
|
|
@@ -24,6 +25,7 @@ from django.views.csrf import csrf_failure as _csrf_failure
|
|
|
24
25
|
from django.views.decorators.csrf import requires_csrf_token
|
|
25
26
|
from django.views.defaults import ERROR_500_TEMPLATE_NAME, page_not_found
|
|
26
27
|
from django.views.generic import TemplateView, View
|
|
28
|
+
from django.views.static import serve
|
|
27
29
|
from graphene_django.views import GraphQLView
|
|
28
30
|
from packaging import version
|
|
29
31
|
from prometheus_client import (
|
|
@@ -133,6 +135,25 @@ class HomeView(AccessMixin, TemplateView):
|
|
|
133
135
|
return self.render_to_response(context)
|
|
134
136
|
|
|
135
137
|
|
|
138
|
+
class MediaView(AccessMixin, View):
|
|
139
|
+
"""
|
|
140
|
+
Serves media files while enforcing login restrictions.
|
|
141
|
+
|
|
142
|
+
This view wraps Django's `serve()` function to ensure that access to media files (with the exception of
|
|
143
|
+
branding files defined in `settings.BRANDING_FILEPATHS`) is restricted to authenticated users.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def get(self, request, path):
|
|
147
|
+
if request.user.is_authenticated:
|
|
148
|
+
return serve(request, path, document_root=settings.MEDIA_ROOT)
|
|
149
|
+
|
|
150
|
+
# Unauthenticated users can access BRANDING_FILEPATHS only
|
|
151
|
+
if posixpath.normpath(path).lstrip("/") in settings.BRANDING_FILEPATHS.values():
|
|
152
|
+
return serve(request, path, document_root=settings.MEDIA_ROOT)
|
|
153
|
+
|
|
154
|
+
return self.handle_no_permission()
|
|
155
|
+
|
|
156
|
+
|
|
136
157
|
class WorkerStatusView(UserPassesTestMixin, TemplateView):
|
|
137
158
|
template_name = "utilities/worker_status.html"
|
|
138
159
|
|
nautobot/core/views/generic.py
CHANGED
|
@@ -54,6 +54,7 @@ from nautobot.core.views.utils import (
|
|
|
54
54
|
check_filter_for_display,
|
|
55
55
|
common_detail_view_context,
|
|
56
56
|
get_csv_form_fields_from_serializer_class,
|
|
57
|
+
get_saved_views_for_user,
|
|
57
58
|
handle_protectederror,
|
|
58
59
|
import_csv_helper,
|
|
59
60
|
prepare_cloned_fields,
|
|
@@ -315,18 +316,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
315
316
|
table_config_form = None
|
|
316
317
|
current_saved_view = None
|
|
317
318
|
current_saved_view_pk = self.request.GET.get("saved_view", None)
|
|
318
|
-
|
|
319
|
-
# User should be able to see any saved view that he has the list view access to.
|
|
320
|
-
if user.has_perms(["extras.view_savedview"]):
|
|
321
|
-
saved_views = SavedView.objects.filter(view=list_url).order_by("name").only("pk", "name")
|
|
322
|
-
else:
|
|
323
|
-
shared_saved_views = (
|
|
324
|
-
SavedView.objects.filter(view=list_url, is_shared=True).order_by("name").only("pk", "name")
|
|
325
|
-
)
|
|
326
|
-
user_owned_saved_views = (
|
|
327
|
-
SavedView.objects.filter(view=list_url, owner=user).order_by("name").only("pk", "name")
|
|
328
|
-
)
|
|
329
|
-
saved_views = shared_saved_views | user_owned_saved_views
|
|
319
|
+
saved_views = get_saved_views_for_user(user, list_url)
|
|
330
320
|
|
|
331
321
|
if current_saved_view_pk:
|
|
332
322
|
try:
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -239,6 +239,9 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
239
239
|
table_class = None
|
|
240
240
|
notes_form_class = NoteForm
|
|
241
241
|
permission_classes = []
|
|
242
|
+
# custom view attributes used for permission checks and handling
|
|
243
|
+
custom_view_base_action = None
|
|
244
|
+
custom_view_additional_permissions = None
|
|
242
245
|
|
|
243
246
|
def get_permissions_for_model(self, model, actions):
|
|
244
247
|
"""
|
|
@@ -249,6 +252,10 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
249
252
|
"""
|
|
250
253
|
model_permissions = []
|
|
251
254
|
for action in actions:
|
|
255
|
+
# Append additional object permissions if specified.
|
|
256
|
+
if self.custom_view_additional_permissions:
|
|
257
|
+
model_permissions.append(*self.custom_view_additional_permissions)
|
|
258
|
+
# Append the model-level permissions for the action.
|
|
252
259
|
model_permissions.append(f"{model._meta.app_label}.{action}_{model._meta.model_name}")
|
|
253
260
|
return model_permissions
|
|
254
261
|
|
|
@@ -501,7 +508,13 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
501
508
|
|
|
502
509
|
def get_action(self):
|
|
503
510
|
"""Helper method for retrieving action and if action not set defaulting to action name."""
|
|
504
|
-
|
|
511
|
+
if self.custom_view_base_action:
|
|
512
|
+
return self.custom_view_base_action
|
|
513
|
+
if self.action in PERMISSIONS_ACTION_MAP:
|
|
514
|
+
# If the action is in the action_map, return the mapped permission
|
|
515
|
+
return PERMISSIONS_ACTION_MAP[self.action]
|
|
516
|
+
|
|
517
|
+
return self.action
|
|
505
518
|
|
|
506
519
|
def get_extra_context(self, request, instance=None):
|
|
507
520
|
"""
|
|
@@ -1032,6 +1045,11 @@ class BulkEditAndBulkDeleteModelMixin:
|
|
|
1032
1045
|
|
|
1033
1046
|
filter_query_params = new_filter_query_params
|
|
1034
1047
|
|
|
1048
|
+
if nullified_fields := request.POST.getlist("_nullify"):
|
|
1049
|
+
form_data["_nullify"] = nullified_fields
|
|
1050
|
+
else:
|
|
1051
|
+
form_data["_nullify"] = []
|
|
1052
|
+
|
|
1035
1053
|
job_form = BulkEditObjects.as_form(
|
|
1036
1054
|
data={
|
|
1037
1055
|
"form_data": form_data,
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -28,6 +28,7 @@ from nautobot.core.views.utils import (
|
|
|
28
28
|
check_filter_for_display,
|
|
29
29
|
common_detail_view_context,
|
|
30
30
|
get_csv_form_fields_from_serializer_class,
|
|
31
|
+
get_saved_views_for_user,
|
|
31
32
|
view_changes_not_saved,
|
|
32
33
|
)
|
|
33
34
|
from nautobot.extras.models import SavedView
|
|
@@ -235,7 +236,8 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
235
236
|
if view.filterset_form_class is not None:
|
|
236
237
|
filter_form = view.filterset_form_class(view.filter_params, label_suffix="")
|
|
237
238
|
table = self.construct_table(view, request=request, permissions=permissions)
|
|
238
|
-
|
|
239
|
+
q_placeholder = "Search " + bettertitle(model._meta.verbose_name_plural)
|
|
240
|
+
search_form = SearchForm(data=view.filter_params, q_placeholder=q_placeholder)
|
|
239
241
|
elif view.action == "destroy":
|
|
240
242
|
form = form_class(initial=request.GET)
|
|
241
243
|
elif view.action in ["create", "update"]:
|
|
@@ -309,18 +311,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
309
311
|
list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
|
|
310
312
|
saved_views = None
|
|
311
313
|
if model.is_saved_view_model:
|
|
312
|
-
|
|
313
|
-
# User should be able to see any saved view that he has the list view access to.
|
|
314
|
-
if request.user.has_perms(["extras.view_savedview"]):
|
|
315
|
-
saved_views = SavedView.objects.filter(view=list_url).order_by("name").only("pk", "name")
|
|
316
|
-
else:
|
|
317
|
-
shared_saved_views = (
|
|
318
|
-
SavedView.objects.filter(view=list_url, is_shared=True).order_by("name").only("pk", "name")
|
|
319
|
-
)
|
|
320
|
-
user_owned_saved_views = (
|
|
321
|
-
SavedView.objects.filter(view=list_url, owner=request.user).order_by("name").only("pk", "name")
|
|
322
|
-
)
|
|
323
|
-
saved_views = shared_saved_views | user_owned_saved_views
|
|
314
|
+
saved_views = get_saved_views_for_user(request.user, list_url)
|
|
324
315
|
|
|
325
316
|
new_changes_not_applied = view_changes_not_saved(request, view, self.saved_view)
|
|
326
317
|
context.update(
|
nautobot/core/views/utils.py
CHANGED
|
@@ -17,6 +17,7 @@ from nautobot.core.utils.data import is_uuid
|
|
|
17
17
|
from nautobot.core.utils.filtering import get_filter_field_label
|
|
18
18
|
from nautobot.core.utils.lookup import get_created_and_last_updated_usernames_for_model, get_form_for_model
|
|
19
19
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
20
|
+
from nautobot.extras.models import SavedView
|
|
20
21
|
from nautobot.extras.tables import AssociatedContactsTable, DynamicGroupTable, ObjectMetadataTable
|
|
21
22
|
|
|
22
23
|
|
|
@@ -382,3 +383,18 @@ def common_detail_view_context(request, instance):
|
|
|
382
383
|
context["associated_object_metadata_table"] = None
|
|
383
384
|
|
|
384
385
|
return context
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def get_saved_views_for_user(user, list_url):
|
|
389
|
+
# We are not using .restrict(request.user, "view") here
|
|
390
|
+
# User should be able to see any saved view that he has the list view access to.
|
|
391
|
+
saved_views = SavedView.objects.filter(view=list_url).order_by("name").only("pk", "name")
|
|
392
|
+
if user.has_perms(["extras.view_savedview"]):
|
|
393
|
+
return saved_views
|
|
394
|
+
|
|
395
|
+
shared_saved_views = saved_views.filter(is_shared=True)
|
|
396
|
+
if user.is_authenticated:
|
|
397
|
+
user_owned_saved_views = SavedView.objects.filter(view=list_url, owner=user).order_by("name").only("pk", "name")
|
|
398
|
+
return shared_saved_views | user_owned_saved_views
|
|
399
|
+
|
|
400
|
+
return shared_saved_views
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -79,6 +79,7 @@ from nautobot.dcim.models import (
|
|
|
79
79
|
Module,
|
|
80
80
|
ModuleBay,
|
|
81
81
|
ModuleBayTemplate,
|
|
82
|
+
ModuleFamily,
|
|
82
83
|
ModuleType,
|
|
83
84
|
PathEndpoint,
|
|
84
85
|
Platform,
|
|
@@ -1124,3 +1125,15 @@ class InterfaceVDCAssignmentSerializer(ValidatedModelSerializer):
|
|
|
1124
1125
|
class Meta:
|
|
1125
1126
|
model = InterfaceVDCAssignment
|
|
1126
1127
|
fields = "__all__"
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
class ModuleFamilySerializer(NautobotModelSerializer):
|
|
1131
|
+
"""API serializer for ModuleFamily objects."""
|
|
1132
|
+
|
|
1133
|
+
url = serializers.HyperlinkedIdentityField(view_name="dcim-api:modulefamily-detail")
|
|
1134
|
+
module_type_count = serializers.IntegerField(read_only=True)
|
|
1135
|
+
module_bay_count = serializers.IntegerField(read_only=True)
|
|
1136
|
+
|
|
1137
|
+
class Meta:
|
|
1138
|
+
model = ModuleFamily
|
|
1139
|
+
fields = "__all__"
|
nautobot/dcim/api/urls.py
CHANGED
|
@@ -17,6 +17,7 @@ router.register("rack-reservations", views.RackReservationViewSet)
|
|
|
17
17
|
router.register("manufacturers", views.ManufacturerViewSet)
|
|
18
18
|
router.register("device-families", views.DeviceFamilyViewSet)
|
|
19
19
|
router.register("device-types", views.DeviceTypeViewSet)
|
|
20
|
+
router.register("module-families", views.ModuleFamilyViewSet)
|
|
20
21
|
router.register("module-types", views.ModuleTypeViewSet)
|
|
21
22
|
|
|
22
23
|
# Device type and Module type components
|
nautobot/dcim/api/views.py
CHANGED
|
@@ -54,6 +54,7 @@ from nautobot.dcim.models import (
|
|
|
54
54
|
Module,
|
|
55
55
|
ModuleBay,
|
|
56
56
|
ModuleBayTemplate,
|
|
57
|
+
ModuleFamily,
|
|
57
58
|
ModuleType,
|
|
58
59
|
Platform,
|
|
59
60
|
PowerFeed,
|
|
@@ -644,6 +645,14 @@ class CableViewSet(NautobotModelViewSet):
|
|
|
644
645
|
serializer_class = serializers.CableSerializer
|
|
645
646
|
filterset_class = filters.CableFilterSet
|
|
646
647
|
|
|
648
|
+
def get_queryset(self):
|
|
649
|
+
# 6933 fix: with prefetch related in queryset
|
|
650
|
+
# DeviceInterface is not properly cleared of _path_id
|
|
651
|
+
queryset = super().get_queryset()
|
|
652
|
+
if self.action == "destroy":
|
|
653
|
+
queryset = queryset.prefetch_related(None)
|
|
654
|
+
return queryset
|
|
655
|
+
|
|
647
656
|
|
|
648
657
|
#
|
|
649
658
|
# Virtual chassis
|
|
@@ -836,3 +845,14 @@ class InterfaceVDCAssignmentViewSet(ModelViewSet):
|
|
|
836
845
|
queryset = InterfaceVDCAssignment.objects.all()
|
|
837
846
|
serializer_class = serializers.InterfaceVDCAssignmentSerializer
|
|
838
847
|
filterset_class = filters.InterfaceVDCAssignmentFilterSet
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
class ModuleFamilyViewSet(NautobotModelViewSet):
|
|
851
|
+
"""API viewset for interacting with ModuleFamily objects."""
|
|
852
|
+
|
|
853
|
+
queryset = ModuleFamily.objects.annotate(
|
|
854
|
+
module_type_count=count_related(ModuleType, "module_family"),
|
|
855
|
+
module_bay_count=count_related(ModuleBay, "module_family"),
|
|
856
|
+
)
|
|
857
|
+
serializer_class = serializers.ModuleFamilySerializer
|
|
858
|
+
filterset_class = filters.ModuleFamilyFilterSet
|
nautobot/dcim/apps.py
CHANGED
nautobot/dcim/factory.py
CHANGED
|
@@ -49,6 +49,7 @@ from nautobot.dcim.models import (
|
|
|
49
49
|
Module,
|
|
50
50
|
ModuleBay,
|
|
51
51
|
ModuleBayTemplate,
|
|
52
|
+
ModuleFamily,
|
|
52
53
|
ModuleType,
|
|
53
54
|
Platform,
|
|
54
55
|
PowerOutletTemplate,
|
|
@@ -787,6 +788,7 @@ class ModuleTypeFactory(PrimaryModelFactory):
|
|
|
787
788
|
exclude = ("has_part_number", "has_comments")
|
|
788
789
|
|
|
789
790
|
manufacturer = random_instance(Manufacturer, allow_null=False)
|
|
791
|
+
module_family = random_instance(ModuleFamily, allow_null=True)
|
|
790
792
|
|
|
791
793
|
has_part_number = NautobotBoolIterator()
|
|
792
794
|
part_number = factory.Maybe("has_part_number", factory.Faker("ean", length=8), "")
|
|
@@ -970,6 +972,15 @@ class ModuleBayTemplateFactory(ModularDeviceComponentTemplateFactory):
|
|
|
970
972
|
factory.LazyAttribute(lambda o: o.module_type.module_bay_templates.count() + 1),
|
|
971
973
|
)
|
|
972
974
|
|
|
975
|
+
class Params:
|
|
976
|
+
has_module_family = NautobotBoolIterator()
|
|
977
|
+
|
|
978
|
+
module_family = factory.Maybe(
|
|
979
|
+
"has_module_family",
|
|
980
|
+
random_instance(ModuleFamily),
|
|
981
|
+
None,
|
|
982
|
+
)
|
|
983
|
+
|
|
973
984
|
|
|
974
985
|
class VirtualDeviceContextFactory(PrimaryModelFactory):
|
|
975
986
|
class Meta:
|
|
@@ -74,6 +74,7 @@ from nautobot.dcim.models import (
|
|
|
74
74
|
Module,
|
|
75
75
|
ModuleBay,
|
|
76
76
|
ModuleBayTemplate,
|
|
77
|
+
ModuleFamily,
|
|
77
78
|
ModuleType,
|
|
78
79
|
Platform,
|
|
79
80
|
PowerFeed,
|
|
@@ -137,6 +138,7 @@ __all__ = (
|
|
|
137
138
|
"ManufacturerFilterSet",
|
|
138
139
|
"ModuleBayFilterSet",
|
|
139
140
|
"ModuleBayTemplateFilterSet",
|
|
141
|
+
"ModuleFamilyFilterSet",
|
|
140
142
|
"ModuleFilterSet",
|
|
141
143
|
"ModuleTypeFilterSet",
|
|
142
144
|
"PathEndpointFilterSet",
|
|
@@ -2072,6 +2074,12 @@ class ModuleFilterSet(
|
|
|
2072
2074
|
to_field_name="model",
|
|
2073
2075
|
label="Module type (model or ID)",
|
|
2074
2076
|
)
|
|
2077
|
+
module_family = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2078
|
+
field_name="module_type__module_family",
|
|
2079
|
+
queryset=ModuleFamily.objects.all(),
|
|
2080
|
+
to_field_name="name",
|
|
2081
|
+
label="Module family (name or ID)",
|
|
2082
|
+
)
|
|
2075
2083
|
parent_module_bay = django_filters.ModelMultipleChoiceFilter(
|
|
2076
2084
|
queryset=ModuleBay.objects.all(),
|
|
2077
2085
|
label="Parent Module Bay",
|
|
@@ -2082,6 +2090,13 @@ class ModuleFilterSet(
|
|
|
2082
2090
|
label="Device (name or ID)",
|
|
2083
2091
|
method="filter_device",
|
|
2084
2092
|
)
|
|
2093
|
+
compatible_with_module_bay = extend_schema_field({"type": "string", "format": "uuid"})(
|
|
2094
|
+
django_filters.ModelChoiceFilter(
|
|
2095
|
+
queryset=ModuleBay.objects.all(),
|
|
2096
|
+
method="filter_module_bay",
|
|
2097
|
+
label="Compatible with module bay (ID)",
|
|
2098
|
+
)
|
|
2099
|
+
)
|
|
2085
2100
|
|
|
2086
2101
|
def _construct_device_filter_recursively(self, field_name, value):
|
|
2087
2102
|
recursion_depth = MODULE_RECURSION_DEPTH_LIMIT
|
|
@@ -2107,6 +2122,12 @@ class ModuleFilterSet(
|
|
|
2107
2122
|
params = self.generate_query_filter_device(value)
|
|
2108
2123
|
return queryset.filter(params)
|
|
2109
2124
|
|
|
2125
|
+
def filter_module_bay(self, queryset, name, value):
|
|
2126
|
+
"""Filter modules based on a module bay's module family."""
|
|
2127
|
+
if value and value.module_family:
|
|
2128
|
+
return queryset.filter(module_type__module_family=value.module_family)
|
|
2129
|
+
return queryset
|
|
2130
|
+
|
|
2110
2131
|
class Meta:
|
|
2111
2132
|
model = Module
|
|
2112
2133
|
fields = "__all__"
|
|
@@ -2133,6 +2154,23 @@ class ModuleTypeFilterSet(DeviceTypeModuleTypeCommonFiltersMixin, NautobotFilter
|
|
|
2133
2154
|
},
|
|
2134
2155
|
},
|
|
2135
2156
|
)
|
|
2157
|
+
manufacturer = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2158
|
+
queryset=Manufacturer.objects.all(),
|
|
2159
|
+
to_field_name="name",
|
|
2160
|
+
label="Manufacturer (name or ID)",
|
|
2161
|
+
)
|
|
2162
|
+
module_family = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2163
|
+
queryset=ModuleFamily.objects.all(),
|
|
2164
|
+
to_field_name="name",
|
|
2165
|
+
label="Module family (name or ID)",
|
|
2166
|
+
)
|
|
2167
|
+
compatible_with_module_bay = extend_schema_field({"type": "string", "format": "uuid"})(
|
|
2168
|
+
django_filters.ModelChoiceFilter(
|
|
2169
|
+
queryset=ModuleBay.objects.all(),
|
|
2170
|
+
method="filter_module_bay",
|
|
2171
|
+
label="Installable in module bay (ID)",
|
|
2172
|
+
)
|
|
2173
|
+
)
|
|
2136
2174
|
has_modules = RelatedMembershipBooleanFilter(
|
|
2137
2175
|
field_name="modules",
|
|
2138
2176
|
label="Has module instances",
|
|
@@ -2142,6 +2180,12 @@ class ModuleTypeFilterSet(DeviceTypeModuleTypeCommonFiltersMixin, NautobotFilter
|
|
|
2142
2180
|
model = ModuleType
|
|
2143
2181
|
fields = "__all__"
|
|
2144
2182
|
|
|
2183
|
+
def filter_module_bay(self, queryset, name, value):
|
|
2184
|
+
"""Filter module types based on a module bay's module family."""
|
|
2185
|
+
if value and value.module_family:
|
|
2186
|
+
return queryset.filter(module_family=value.module_family)
|
|
2187
|
+
return queryset
|
|
2188
|
+
|
|
2145
2189
|
|
|
2146
2190
|
class ModuleBayTemplateFilterSet(ModularDeviceComponentTemplateModelFilterSetMixin, NautobotFilterSet):
|
|
2147
2191
|
q = SearchFilter(
|
|
@@ -2172,6 +2216,14 @@ class ModuleBayTemplateFilterSet(ModularDeviceComponentTemplateModelFilterSetMix
|
|
|
2172
2216
|
},
|
|
2173
2217
|
}
|
|
2174
2218
|
)
|
|
2219
|
+
module_family = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2220
|
+
queryset=ModuleFamily.objects.all(),
|
|
2221
|
+
to_field_name="name",
|
|
2222
|
+
label="Module family (name or ID)",
|
|
2223
|
+
)
|
|
2224
|
+
requires_first_party_modules = django_filters.BooleanFilter(
|
|
2225
|
+
label="Requires first-party modules",
|
|
2226
|
+
)
|
|
2175
2227
|
|
|
2176
2228
|
class Meta:
|
|
2177
2229
|
model = ModuleBayTemplate
|
|
@@ -2228,6 +2280,15 @@ class ModuleBayFilterSet(NautobotFilterSet):
|
|
|
2228
2280
|
field_name="installed_module",
|
|
2229
2281
|
label="Has installed module",
|
|
2230
2282
|
)
|
|
2283
|
+
module_family = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2284
|
+
queryset=ModuleFamily.objects.all(),
|
|
2285
|
+
to_field_name="name",
|
|
2286
|
+
label="Module family (name or ID)",
|
|
2287
|
+
)
|
|
2288
|
+
requires_first_party_modules = django_filters.BooleanFilter(
|
|
2289
|
+
field_name="requires_first_party_modules",
|
|
2290
|
+
label="Requires first-party modules",
|
|
2291
|
+
)
|
|
2231
2292
|
|
|
2232
2293
|
class Meta:
|
|
2233
2294
|
model = ModuleBay
|
|
@@ -2362,3 +2423,52 @@ class InterfaceVDCAssignmentFilterSet(NautobotFilterSet):
|
|
|
2362
2423
|
"interface",
|
|
2363
2424
|
"virtual_device_context",
|
|
2364
2425
|
]
|
|
2426
|
+
|
|
2427
|
+
|
|
2428
|
+
class ModuleFamilyFilterSet(NautobotFilterSet):
|
|
2429
|
+
"""FilterSet for ModuleFamily objects."""
|
|
2430
|
+
|
|
2431
|
+
q = SearchFilter(
|
|
2432
|
+
filter_predicates={
|
|
2433
|
+
"name": {
|
|
2434
|
+
"lookup_expr": "icontains",
|
|
2435
|
+
"preprocessor": str.strip,
|
|
2436
|
+
},
|
|
2437
|
+
"description": {
|
|
2438
|
+
"lookup_expr": "icontains",
|
|
2439
|
+
"preprocessor": str.strip,
|
|
2440
|
+
},
|
|
2441
|
+
"module_types__model": {
|
|
2442
|
+
"lookup_expr": "icontains",
|
|
2443
|
+
"preprocessor": str.strip,
|
|
2444
|
+
},
|
|
2445
|
+
"module_types__manufacturer__name": {
|
|
2446
|
+
"lookup_expr": "icontains",
|
|
2447
|
+
"preprocessor": str.strip,
|
|
2448
|
+
},
|
|
2449
|
+
}
|
|
2450
|
+
)
|
|
2451
|
+
|
|
2452
|
+
module_types = NaturalKeyOrPKMultipleChoiceFilter(
|
|
2453
|
+
queryset=ModuleType.objects.all(),
|
|
2454
|
+
to_field_name="model",
|
|
2455
|
+
label="Module types (model or ID)",
|
|
2456
|
+
)
|
|
2457
|
+
|
|
2458
|
+
module_bay_id = extend_schema_field({"type": "array", "items": {"type": "string", "format": "uuid"}})(
|
|
2459
|
+
django_filters.ModelMultipleChoiceFilter(
|
|
2460
|
+
queryset=ModuleBay.objects.all(),
|
|
2461
|
+
label="Module bay (ID)",
|
|
2462
|
+
)
|
|
2463
|
+
)
|
|
2464
|
+
|
|
2465
|
+
class Meta:
|
|
2466
|
+
model = ModuleFamily
|
|
2467
|
+
fields = [
|
|
2468
|
+
"id",
|
|
2469
|
+
"name",
|
|
2470
|
+
"description",
|
|
2471
|
+
"module_types",
|
|
2472
|
+
"module_bay_id",
|
|
2473
|
+
"tags",
|
|
2474
|
+
]
|