nautobot 2.4.5__py3-none-any.whl → 2.4.7__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/apps/forms.py +2 -0
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +2 -55
- nautobot/circuits/views.py +20 -23
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +2 -40
- nautobot/cloud/views.py +12 -0
- nautobot/core/api/mixins.py +10 -0
- nautobot/core/api/urls.py +2 -2
- nautobot/core/api/views.py +39 -1
- nautobot/core/celery/encoders.py +2 -2
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/fields.py +23 -7
- nautobot/core/forms/utils.py +1 -0
- nautobot/core/forms/widgets.py +18 -0
- nautobot/core/jobs/bulk_actions.py +1 -1
- nautobot/core/management/commands/generate_test_data.py +1 -1
- nautobot/core/models/name_color_content_types.py +9 -0
- nautobot/core/models/validators.py +7 -0
- nautobot/core/settings.py +0 -14
- nautobot/core/settings.yaml +0 -28
- nautobot/core/tables.py +6 -1
- nautobot/core/templates/generic/object_retrieve.html +1 -1
- nautobot/core/templates/widgets/sluginput.html +5 -1
- nautobot/core/templatetags/helpers.py +15 -1
- nautobot/core/testing/api.py +18 -0
- nautobot/core/testing/integration.py +6 -2
- nautobot/core/tests/nautobot_config.py +0 -2
- nautobot/core/tests/runner.py +17 -140
- nautobot/core/tests/test_api.py +4 -4
- nautobot/core/tests/test_authentication.py +83 -4
- nautobot/core/tests/test_forms.py +11 -8
- nautobot/core/tests/test_graphql.py +9 -0
- nautobot/core/tests/test_jobs.py +7 -0
- nautobot/core/ui/object_detail.py +47 -3
- nautobot/core/utils/lookup.py +2 -2
- nautobot/dcim/factory.py +2 -0
- nautobot/dcim/filters/__init__.py +5 -0
- nautobot/dcim/forms.py +27 -1
- nautobot/dcim/migrations/0068_alter_softwareimagefile_download_url.py +19 -0
- nautobot/dcim/migrations/0069_softwareimagefile_external_integration.py +25 -0
- nautobot/dcim/models/devices.py +9 -2
- nautobot/dcim/models/locations.py +9 -0
- nautobot/dcim/tables/devices.py +1 -0
- nautobot/dcim/templates/dcim/device_list.html +1 -1
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/manufacturer.html +1 -63
- nautobot/dcim/templates/dcim/module_list.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +4 -0
- nautobot/dcim/tests/integration/test_module_bay_position.py +125 -0
- nautobot/dcim/tests/test_api.py +74 -31
- nautobot/dcim/tests/test_filters.py +2 -0
- nautobot/dcim/tests/test_models.py +78 -0
- nautobot/dcim/tests/test_views.py +7 -1
- nautobot/dcim/urls.py +1 -45
- nautobot/dcim/views.py +35 -66
- nautobot/extras/choices.py +4 -0
- nautobot/extras/filters/__init__.py +6 -0
- nautobot/extras/forms/forms.py +83 -11
- nautobot/extras/models/customfields.py +10 -9
- nautobot/extras/plugins/marketplace_manifest.yml +18 -0
- nautobot/extras/signals.py +43 -4
- nautobot/extras/tables.py +4 -5
- nautobot/extras/tasks.py +4 -2
- nautobot/extras/templates/extras/contact_retrieve.html +1 -58
- nautobot/extras/templates/extras/exporttemplate.html +1 -53
- nautobot/extras/templates/extras/inc/panel_changelog.html +1 -1
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
- nautobot/extras/templates/extras/status.html +1 -37
- nautobot/extras/templates/extras/team_retrieve.html +1 -58
- nautobot/extras/tests/integration/test_notes.py +1 -1
- nautobot/extras/tests/test_api.py +22 -7
- nautobot/extras/tests/test_changelog.py +4 -4
- nautobot/extras/tests/test_customfields.py +27 -0
- nautobot/extras/tests/test_plugins.py +19 -13
- nautobot/extras/tests/test_relationships.py +9 -0
- nautobot/extras/tests/test_tags.py +2 -2
- nautobot/extras/tests/test_views.py +37 -6
- nautobot/extras/urls.py +3 -100
- nautobot/extras/views.py +111 -133
- nautobot/ipam/tables.py +7 -2
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -41
- nautobot/ipam/templates/ipam/service.html +2 -46
- nautobot/ipam/templates/ipam/service_edit.html +1 -17
- nautobot/ipam/templates/ipam/service_retrieve.html +7 -0
- nautobot/ipam/tests/migration/__init__.py +0 -0
- nautobot/ipam/tests/migration/test_migrations.py +510 -0
- nautobot/ipam/tests/test_api.py +66 -36
- nautobot/ipam/tests/test_filters.py +0 -10
- nautobot/ipam/tests/test_views.py +44 -2
- nautobot/ipam/urls.py +2 -47
- nautobot/ipam/utils/migrations.py +185 -152
- nautobot/ipam/utils/testing.py +177 -0
- nautobot/ipam/views.py +95 -157
- nautobot/project-static/css/base.css +5 -0
- nautobot/project-static/docs/404.html +0 -2
- nautobot/project-static/docs/apps/index.html +0 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +62 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +47 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +18 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +70 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +0 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +0 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +0 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +0 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +0 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +0 -2
- nautobot/project-static/docs/development/apps/api/setup.html +0 -2
- nautobot/project-static/docs/development/apps/api/testing.html +0 -89
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +0 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +0 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +0 -2
- nautobot/project-static/docs/development/apps/index.html +0 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +0 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +1 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +0 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +0 -2
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +0 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +0 -2
- nautobot/project-static/docs/development/core/application-registry.html +0 -2
- nautobot/project-static/docs/development/core/best-practices.html +3 -5
- nautobot/project-static/docs/development/core/bootstrap-ui.html +0 -2
- nautobot/project-static/docs/development/core/caching.html +0 -2
- nautobot/project-static/docs/development/core/controllers.html +0 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +0 -2
- nautobot/project-static/docs/development/core/generic-views.html +0 -2
- nautobot/project-static/docs/development/core/getting-started.html +78 -109
- nautobot/project-static/docs/development/core/homepage.html +0 -2
- nautobot/project-static/docs/development/core/index.html +0 -2
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +0 -2
- nautobot/project-static/docs/development/core/model-checklist.html +0 -2
- nautobot/project-static/docs/development/core/model-features.html +0 -2
- nautobot/project-static/docs/development/core/natural-keys.html +0 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +0 -2
- nautobot/project-static/docs/development/core/release-checklist.html +1 -3
- nautobot/project-static/docs/development/core/role-internals.html +0 -2
- nautobot/project-static/docs/development/core/settings.html +0 -2
- nautobot/project-static/docs/development/core/style-guide.html +1 -3
- nautobot/project-static/docs/development/core/templates.html +0 -2
- nautobot/project-static/docs/development/core/testing.html +24 -200
- nautobot/project-static/docs/development/core/ui-component-framework.html +0 -2
- nautobot/project-static/docs/development/core/user-preferences.html +0 -2
- nautobot/project-static/docs/development/index.html +0 -2
- nautobot/project-static/docs/development/jobs/index.html +0 -2
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +0 -2
- nautobot/project-static/docs/index.html +0 -2
- nautobot/project-static/docs/media/user-guide/administration/getting-started/nautobot-cloud.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +1 -3
- nautobot/project-static/docs/overview/design_philosophy.html +0 -2
- nautobot/project-static/docs/release-notes/index.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +0 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.3.html +0 -2
- nautobot/project-static/docs/release-notes/version-2.4.html +364 -3
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +0 -2
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +2 -50
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +71 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +0 -2
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +257 -18
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +0 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +0 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +0 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +0 -2
- nautobot/project-static/docs/user-guide/administration/security/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/security/notices.html +0 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +1 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +0 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +0 -2
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +11 -13
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +8 -10
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +1 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +40 -27
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +1 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +77 -7
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +1 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +0 -3
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +0 -2
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +0 -2
- nautobot/project-static/docs/user-guide/index.html +89 -4
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +0 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +207 -124
- nautobot/project-static/js/forms.js +88 -37
- nautobot/project-static/js/homepage_layout.js +12 -3
- nautobot/virtualization/forms.py +20 -0
- nautobot/virtualization/templates/virtualization/clustergroup.html +1 -39
- nautobot/virtualization/templates/virtualization/clustertype.html +1 -0
- nautobot/virtualization/tests/test_api.py +14 -3
- nautobot/virtualization/tests/test_views.py +10 -2
- nautobot/virtualization/urls.py +10 -93
- nautobot/virtualization/views.py +33 -72
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/METADATA +6 -5
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/RECORD +407 -402
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/WHEEL +1 -1
- nautobot/core/tests/performance_baselines.yml +0 -8900
- nautobot/dcim/templates/dcim/modulebay_create.html +0 -39
- nautobot/ipam/tests/test_migrations.py +0 -462
- /nautobot/ipam/templates/ipam/{namespace_ipaddresses.html → namespace_ip_addresses.html} +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/NOTICE +0 -0
- {nautobot-2.4.5.dist-info → nautobot-2.4.7.dist-info}/entry_points.txt +0 -0
|
@@ -2,7 +2,7 @@ import datetime
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
5
|
-
from urllib.parse import parse_qs
|
|
5
|
+
from urllib.parse import parse_qs, quote_plus
|
|
6
6
|
|
|
7
7
|
from django import template
|
|
8
8
|
from django.conf import settings
|
|
@@ -711,6 +711,20 @@ def render_ancestor_hierarchy(value):
|
|
|
711
711
|
return result
|
|
712
712
|
|
|
713
713
|
|
|
714
|
+
@library.filter()
|
|
715
|
+
@register.filter()
|
|
716
|
+
def render_address(address):
|
|
717
|
+
if address:
|
|
718
|
+
map_link = format_html(
|
|
719
|
+
'<a href="https://maps.google.com/?q={}" target="_blank" class="btn btn-primary btn-xs">'
|
|
720
|
+
'<i class="mdi mdi-map-marker"></i> Map it</a>',
|
|
721
|
+
quote_plus(address),
|
|
722
|
+
)
|
|
723
|
+
address = format_html_join("", "{}<br>", ((line,) for line in address.split("\n")))
|
|
724
|
+
return format_html('<div class="pull-right noprint">{}</div>{}', map_link, address)
|
|
725
|
+
return HTML_NONE
|
|
726
|
+
|
|
727
|
+
|
|
714
728
|
#
|
|
715
729
|
# Tags
|
|
716
730
|
#
|
nautobot/core/testing/api.py
CHANGED
|
@@ -702,6 +702,9 @@ class APIViewTestCases:
|
|
|
702
702
|
else:
|
|
703
703
|
self.assertEqual(obj.key, expected_slug)
|
|
704
704
|
|
|
705
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
706
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
707
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
705
708
|
def test_create_object(self):
|
|
706
709
|
"""
|
|
707
710
|
POST a single object with permission.
|
|
@@ -738,6 +741,9 @@ class APIViewTestCases:
|
|
|
738
741
|
self.assertEqual(len(objectchanges), 1)
|
|
739
742
|
self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_CREATE)
|
|
740
743
|
|
|
744
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
745
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
746
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
741
747
|
def test_recreate_object_csv(self):
|
|
742
748
|
"""CSV export an object, delete it, and recreate it via CSV import."""
|
|
743
749
|
if hasattr(self, "get_deletable_object"):
|
|
@@ -801,6 +807,9 @@ class APIViewTestCases:
|
|
|
801
807
|
f"{field_name} should have been unchanged on delete/recreate but it differs!",
|
|
802
808
|
)
|
|
803
809
|
|
|
810
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
811
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
812
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
804
813
|
def test_bulk_create_objects(self):
|
|
805
814
|
"""
|
|
806
815
|
POST a set of objects in a single request.
|
|
@@ -853,6 +862,9 @@ class APIViewTestCases:
|
|
|
853
862
|
response = self.client.patch(url, update_data, format="json", **self.header)
|
|
854
863
|
self.assertHttpStatus(response, status.HTTP_403_FORBIDDEN)
|
|
855
864
|
|
|
865
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
866
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
867
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
856
868
|
def test_update_object(self):
|
|
857
869
|
"""
|
|
858
870
|
PATCH a single object identified by its ID.
|
|
@@ -965,6 +977,9 @@ class APIViewTestCases:
|
|
|
965
977
|
instance.refresh_from_db()
|
|
966
978
|
self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
|
|
967
979
|
|
|
980
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
981
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
982
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
968
983
|
def test_get_put_round_trip(self):
|
|
969
984
|
"""GET and then PUT an object and verify that it's accepted and unchanged."""
|
|
970
985
|
self.maxDiff = None
|
|
@@ -995,6 +1010,9 @@ class APIViewTestCases:
|
|
|
995
1010
|
updated_serialized_object.pop("last_updated", None)
|
|
996
1011
|
self.assertEqual(initial_serialized_object, updated_serialized_object)
|
|
997
1012
|
|
|
1013
|
+
# TODO: The override_settings here is a temporary workaround for not breaking any app tests
|
|
1014
|
+
# long term fix should be using appropriate object permissions instead of the blanket override
|
|
1015
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
998
1016
|
def test_bulk_update_objects(self):
|
|
999
1017
|
"""
|
|
1000
1018
|
PATCH a set of objects in a single request.
|
|
@@ -35,11 +35,15 @@ class ObjectsListMixin:
|
|
|
35
35
|
"""
|
|
36
36
|
self.browser.find_by_css("#object_list_form input.toggle").click()
|
|
37
37
|
|
|
38
|
-
def select_one_item(self):
|
|
38
|
+
def select_one_item(self, pk=None):
|
|
39
39
|
"""
|
|
40
40
|
Click first row checkbox on items table list to select one row.
|
|
41
41
|
"""
|
|
42
|
-
|
|
42
|
+
selector = '#object_list_form input[name="pk"]'
|
|
43
|
+
if pk:
|
|
44
|
+
selector = f'{selector}[value="{pk}"]'
|
|
45
|
+
|
|
46
|
+
self.browser.find_by_css(selector).click()
|
|
43
47
|
|
|
44
48
|
def set_per_page(self, per_page=1):
|
|
45
49
|
"""
|
|
@@ -44,8 +44,6 @@ CONSTANCE_BACKEND = "constance.backends.memory.MemoryBackend"
|
|
|
44
44
|
TEST_USE_FACTORIES = True
|
|
45
45
|
# For now, use a constant PRNG seed for consistent results. In the future we can remove this for fuzzier testing.
|
|
46
46
|
TEST_FACTORY_SEED = "Nautobot"
|
|
47
|
-
# File in which all performance-specifc test baselines are stored
|
|
48
|
-
TEST_PERFORMANCE_BASELINE_FILE = "nautobot/core/tests/performance_baselines.yml"
|
|
49
47
|
|
|
50
48
|
# Make Celery run synchronously (eager), to always store eager results, and run the broker in-memory.
|
|
51
49
|
# NOTE: Celery does not honor the TASK_TRACK_STARTED config when running in eager mode, so the job result is not saved until after the task completes.
|
nautobot/core/tests/runner.py
CHANGED
|
@@ -14,7 +14,6 @@ from django.db import connections
|
|
|
14
14
|
from django.db.migrations.recorder import MigrationRecorder
|
|
15
15
|
from django.test.runner import _init_worker, DiscoverRunner, ParallelTestSuite
|
|
16
16
|
from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper, override_settings
|
|
17
|
-
import yaml
|
|
18
17
|
|
|
19
18
|
from nautobot.core.celery import app, setup_nautobot_job_logging
|
|
20
19
|
from nautobot.core.settings_funcs import parse_redis_connection
|
|
@@ -40,21 +39,25 @@ class NautobotParallelTestSuite(ParallelTestSuite):
|
|
|
40
39
|
|
|
41
40
|
class NautobotTestRunner(DiscoverRunner):
|
|
42
41
|
"""
|
|
43
|
-
Custom test runner that excludes integration tests by default.
|
|
42
|
+
Custom test runner that excludes (slow) integration and migration tests by default.
|
|
44
43
|
|
|
45
44
|
This test runner is aware of our use of the "integration" tag and only runs integration tests if
|
|
46
45
|
explicitly passed in with `nautobot-server test --tag integration`.
|
|
46
|
+
Similarly, it only runs migration tests if explicitly called with `--tag migration_test`.
|
|
47
47
|
|
|
48
48
|
By Nautobot convention, integration tests must be tagged with "integration". The base
|
|
49
49
|
`nautobot.core.testing.integration.SeleniumTestCase` has this tag, therefore any test cases
|
|
50
50
|
inheriting from that class do not need to be explicitly tagged.
|
|
51
51
|
|
|
52
52
|
Only integration tests that DO NOT inherit from `SeleniumTestCase` will need to be explicitly tagged.
|
|
53
|
+
|
|
54
|
+
Similarly, the `django-test-migrations` package `MigratorTestCase` base class has the tag `migration_test`, so
|
|
55
|
+
any subclasses thereof do not need to be explicitly tagged.
|
|
53
56
|
"""
|
|
54
57
|
|
|
55
58
|
parallel_test_suite = NautobotParallelTestSuite
|
|
56
59
|
|
|
57
|
-
exclude_tags = ["integration"]
|
|
60
|
+
exclude_tags = ["integration", "migration_test"]
|
|
58
61
|
|
|
59
62
|
@classmethod
|
|
60
63
|
def add_arguments(cls, parser):
|
|
@@ -75,15 +78,19 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
75
78
|
self.cache_test_fixtures = cache_test_fixtures
|
|
76
79
|
self.reusedb = reusedb
|
|
77
80
|
|
|
78
|
-
# Assert "integration" hasn't been provided w/ --tag
|
|
79
81
|
incoming_tags = kwargs.get("tags") or []
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
exclude_tags = kwargs.get("exclude_tags") or []
|
|
83
|
+
|
|
84
|
+
for default_excluded_tag in self.exclude_tags:
|
|
85
|
+
if default_excluded_tag not in incoming_tags:
|
|
86
|
+
exclude_tags.append(default_excluded_tag)
|
|
87
|
+
# Can't just use self.log() here because we haven't yet called super().__init__()
|
|
88
|
+
if logger := kwargs.get("logger"):
|
|
89
|
+
logger.info("Implicitly excluding tests tagged %r", default_excluded_tag)
|
|
90
|
+
elif kwargs.get("verbosity", 1) >= 1:
|
|
91
|
+
print(f"Implicitly excluding tests tagged {default_excluded_tag!r}")
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
if "integration" not in incoming_tags:
|
|
85
|
-
incoming_exclude_tags.extend(self.exclude_tags)
|
|
86
|
-
kwargs["exclude_tags"] = incoming_exclude_tags
|
|
93
|
+
kwargs["exclude_tags"] = exclude_tags
|
|
87
94
|
|
|
88
95
|
super().__init__(**kwargs)
|
|
89
96
|
|
|
@@ -202,133 +209,3 @@ class NautobotTestRunner(DiscoverRunner):
|
|
|
202
209
|
print(f"Database {db_name} emptied!")
|
|
203
210
|
|
|
204
211
|
connection.creation.destroy_test_db(old_name, self.verbosity, self.keepdb)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
# Use django_slowtests only when GENERATE_PERFORMANCE_REPORT flag is set to true
|
|
208
|
-
try:
|
|
209
|
-
from django_slowtests.testrunner import DiscoverSlowestTestsRunner
|
|
210
|
-
|
|
211
|
-
print("Using NautobotPerformanceTestRunner to run tests ...")
|
|
212
|
-
|
|
213
|
-
class NautobotPerformanceTestRunner(NautobotTestRunner, DiscoverSlowestTestsRunner):
|
|
214
|
-
"""
|
|
215
|
-
Pre-requisite:
|
|
216
|
-
Set `GENERATE_PERFORMANCE_REPORT` to True in settings.py
|
|
217
|
-
This test runner is designated to run performance specific unit tests.
|
|
218
|
-
|
|
219
|
-
`ModelViewTestCase` is tagged with `performance` to test the time it will take to retrieve, list, create, bulk_create,
|
|
220
|
-
delete, bulk_delete, edit, bulk_edit object(s) and various other operations.
|
|
221
|
-
|
|
222
|
-
The results are compared to the corresponding entries in `TEST_PERFORMANCE_BASELINE_FILE` and only results that are significantly slower
|
|
223
|
-
than baseline will be exposed to the user.
|
|
224
|
-
"""
|
|
225
|
-
|
|
226
|
-
def generate_report(self, test_results, result):
|
|
227
|
-
"""
|
|
228
|
-
Generate Performance Report consists of unit tests that are significantly slower than baseline.
|
|
229
|
-
"""
|
|
230
|
-
test_result_count = len(test_results)
|
|
231
|
-
|
|
232
|
-
# Add `--performance-snapshot` to the end of `invoke` commands to generate a report.json file consist of the performance tests result
|
|
233
|
-
if self.report_path:
|
|
234
|
-
data = [
|
|
235
|
-
{
|
|
236
|
-
"tests": [
|
|
237
|
-
{
|
|
238
|
-
"name": func_name,
|
|
239
|
-
"execution_time": float(timing),
|
|
240
|
-
}
|
|
241
|
-
for func_name, timing in test_results
|
|
242
|
-
],
|
|
243
|
-
"test_count": result.testsRun,
|
|
244
|
-
"failed_count": len(result.errors + result.failures),
|
|
245
|
-
"total_execution_time": result.timeTaken,
|
|
246
|
-
}
|
|
247
|
-
]
|
|
248
|
-
with open(self.report_path, "w") as outfile:
|
|
249
|
-
yaml.dump(data, outfile, sort_keys=False)
|
|
250
|
-
# Print the results in the CLI.
|
|
251
|
-
else:
|
|
252
|
-
if test_result_count:
|
|
253
|
-
print(f"\n{test_result_count} abnormally slower tests:")
|
|
254
|
-
for func_name, timing in test_results:
|
|
255
|
-
time = float(timing)
|
|
256
|
-
baseline = self.baselines.get(func_name, None)
|
|
257
|
-
if baseline:
|
|
258
|
-
baseline = float(baseline)
|
|
259
|
-
print(f"{time:.4f}s {func_name} is significantly slower than the baseline {baseline:.4f}s")
|
|
260
|
-
else:
|
|
261
|
-
print(
|
|
262
|
-
f"Performance baseline for {func_name} is not available. Test took {time:.4f}s to run"
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
if not test_results:
|
|
266
|
-
print("\nNo tests signficantly slower than baseline. Success!")
|
|
267
|
-
|
|
268
|
-
def get_baselines(self):
|
|
269
|
-
"""Load the performance_baselines.yml file for result comparison."""
|
|
270
|
-
baselines = {}
|
|
271
|
-
input_file = getattr(
|
|
272
|
-
settings, "TEST_PERFORMANCE_BASELINE_FILE", "nautobot/core/tests/performance_baselines.yml"
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
with open(input_file) as f:
|
|
276
|
-
data = yaml.safe_load(f)
|
|
277
|
-
for entry in data["tests"]:
|
|
278
|
-
baselines[entry["name"]] = entry["execution_time"]
|
|
279
|
-
return baselines
|
|
280
|
-
|
|
281
|
-
def suite_result(self, suite, result):
|
|
282
|
-
"""Compile the performance test results"""
|
|
283
|
-
return_value = super(DiscoverSlowestTestsRunner, self).suite_result(suite, result)
|
|
284
|
-
self.baselines = self.get_baselines()
|
|
285
|
-
|
|
286
|
-
# add `--performance_report` to `invoke` commands to generate report.
|
|
287
|
-
# e.g. `invoke unittest --performance_report`
|
|
288
|
-
if not self.should_generate_report:
|
|
289
|
-
self.remove_timing_tmp_files()
|
|
290
|
-
return return_value
|
|
291
|
-
|
|
292
|
-
# Grab slowest tests
|
|
293
|
-
timings = self.get_timings()
|
|
294
|
-
# Sort the results by test names x[0]
|
|
295
|
-
by_name = sorted(timings, key=lambda x: x[0])
|
|
296
|
-
test_results = by_name
|
|
297
|
-
|
|
298
|
-
if self.baselines:
|
|
299
|
-
# Filter tests by baseline numbers
|
|
300
|
-
test_results = []
|
|
301
|
-
|
|
302
|
-
for entry in by_name:
|
|
303
|
-
# Convert test time from seconds to miliseconds for comparison
|
|
304
|
-
result_time_ms = entry[1] * 1000
|
|
305
|
-
# If self.report_path, that means the user wants to update the performance baselines.
|
|
306
|
-
# so we append every result that is available to us.
|
|
307
|
-
if self.report_path:
|
|
308
|
-
test_results.append(entry)
|
|
309
|
-
else:
|
|
310
|
-
# If the test is completed under 1.5 times the baseline or the difference between the result and the baseline is less than 3 seconds,
|
|
311
|
-
# dont show the test to the user.
|
|
312
|
-
|
|
313
|
-
baseline = self.baselines.get(entry[0], None)
|
|
314
|
-
|
|
315
|
-
# check if baseline is available
|
|
316
|
-
if not baseline:
|
|
317
|
-
test_results.append(entry)
|
|
318
|
-
continue
|
|
319
|
-
|
|
320
|
-
# baseline duration in milliseconds
|
|
321
|
-
baseline_ms = baseline * 1000
|
|
322
|
-
# Arbitrary criteria to not make performance test fail easily
|
|
323
|
-
if result_time_ms <= baseline_ms * 1.5 or result_time_ms - baseline_ms <= 500:
|
|
324
|
-
continue
|
|
325
|
-
|
|
326
|
-
test_results.append(entry)
|
|
327
|
-
|
|
328
|
-
self.generate_report(test_results, result)
|
|
329
|
-
return return_value
|
|
330
|
-
|
|
331
|
-
except ImportError:
|
|
332
|
-
print(
|
|
333
|
-
"Unable to import DiscoverSlowestTestsRunner from `django_slowtests`. Is the 'django_slowtests' package installed?"
|
|
334
|
-
)
|
nautobot/core/tests/test_api.py
CHANGED
|
@@ -639,7 +639,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
|
|
|
639
639
|
"vlan_group": self.vlan_group1.pk,
|
|
640
640
|
}
|
|
641
641
|
url = reverse("ipam-api:vlan-list")
|
|
642
|
-
self.add_permissions("ipam.add_vlan")
|
|
642
|
+
self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
|
|
643
643
|
|
|
644
644
|
response = self.client.post(url, data, format="json", **self.header)
|
|
645
645
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
@@ -672,7 +672,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
|
|
|
672
672
|
"vlan_group": {"name": self.vlan_group1.name},
|
|
673
673
|
}
|
|
674
674
|
url = reverse("ipam-api:vlan-list")
|
|
675
|
-
self.add_permissions("ipam.add_vlan")
|
|
675
|
+
self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
|
|
676
676
|
|
|
677
677
|
response = self.client.post(url, data, format="json", **self.header)
|
|
678
678
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
@@ -708,7 +708,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
|
|
|
708
708
|
},
|
|
709
709
|
}
|
|
710
710
|
url = reverse("ipam-api:vlan-list")
|
|
711
|
-
self.add_permissions("ipam.add_vlan")
|
|
711
|
+
self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
|
|
712
712
|
|
|
713
713
|
with testing.disable_warnings("django.request"):
|
|
714
714
|
response = self.client.post(url, data, format="json", **self.header)
|
|
@@ -775,7 +775,7 @@ class WritableNestedSerializerTest(testing.APITestCase):
|
|
|
775
775
|
"vlan_group": self.vlan_group1.pk,
|
|
776
776
|
}
|
|
777
777
|
url = reverse("ipam-api:vlan-list")
|
|
778
|
-
self.add_permissions("ipam.add_vlan")
|
|
778
|
+
self.add_permissions("ipam.add_vlan", "ipam.view_vlangroup", "extras.view_status")
|
|
779
779
|
|
|
780
780
|
response = self.client.post(url, data, format="json", **self.header)
|
|
781
781
|
self.assertHttpStatus(response, status.HTTP_201_CREATED)
|
|
@@ -383,14 +383,31 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
383
383
|
)
|
|
384
384
|
obj_perm.users.add(self.user)
|
|
385
385
|
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
|
|
386
|
-
|
|
386
|
+
related_obj_perm = ObjectPermission.objects.create(
|
|
387
|
+
name="Related object permission",
|
|
388
|
+
actions=["view"],
|
|
389
|
+
)
|
|
390
|
+
related_obj_perm.users.add(self.user)
|
|
391
|
+
related_obj_perm.object_types.add(
|
|
392
|
+
ContentType.objects.get_for_model(Namespace),
|
|
393
|
+
ContentType.objects.get_for_model(Status),
|
|
394
|
+
ContentType.objects.get_for_model(Location),
|
|
395
|
+
)
|
|
387
396
|
# Attempt to create a non-permitted object
|
|
388
397
|
response = self.client.post(url, data, format="json", **self.header)
|
|
389
398
|
self.assertEqual(response.status_code, 403)
|
|
390
399
|
self.assertEqual(Prefix.objects.count(), initial_count)
|
|
391
400
|
|
|
392
|
-
#
|
|
401
|
+
# Attempt to create a permitted object without related object permissions
|
|
393
402
|
data["location"] = self.locations[0].pk
|
|
403
|
+
related_obj_perm.users.remove(self.user)
|
|
404
|
+
response = self.client.post(url, data, format="json", **self.header)
|
|
405
|
+
self.assertEqual(response.status_code, 400)
|
|
406
|
+
self.assertIn(b"Related object not found using the provided attribute", response.content)
|
|
407
|
+
self.assertEqual(Prefix.objects.count(), initial_count)
|
|
408
|
+
|
|
409
|
+
# Create a permitted object with related object permissions
|
|
410
|
+
related_obj_perm.users.add(self.user)
|
|
394
411
|
response = self.client.post(url, data, format="json", **self.header)
|
|
395
412
|
self.assertEqual(response.status_code, 201)
|
|
396
413
|
self.assertEqual(Prefix.objects.count(), initial_count + 1)
|
|
@@ -411,17 +428,33 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
411
428
|
)
|
|
412
429
|
obj_perm.users.add(self.user)
|
|
413
430
|
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
|
|
414
|
-
|
|
431
|
+
related_obj_perm = ObjectPermission.objects.create(
|
|
432
|
+
name="Related object permission",
|
|
433
|
+
actions=["view"],
|
|
434
|
+
)
|
|
435
|
+
related_obj_perm.users.add(self.user)
|
|
436
|
+
related_obj_perm.object_types.add(
|
|
437
|
+
ContentType.objects.get_for_model(Namespace),
|
|
438
|
+
ContentType.objects.get_for_model(Status),
|
|
439
|
+
ContentType.objects.get_for_model(Location),
|
|
440
|
+
)
|
|
415
441
|
# Attempt to edit a non-permitted object
|
|
416
442
|
data = {"location": self.locations[0].pk}
|
|
417
443
|
url = reverse("ipam-api:prefix-detail", kwargs={"pk": self.prefixes[3].pk})
|
|
418
444
|
response = self.client.patch(url, data, format="json", **self.header)
|
|
419
445
|
self.assertEqual(response.status_code, 404)
|
|
420
446
|
|
|
421
|
-
#
|
|
447
|
+
# Attempt to edit a permitted object without related object permissions
|
|
448
|
+
related_obj_perm.users.remove(self.user)
|
|
422
449
|
data["status"] = self.statuses[1].pk
|
|
423
450
|
url = reverse("ipam-api:prefix-detail", kwargs={"pk": self.prefixes[0].pk})
|
|
424
451
|
response = self.client.patch(url, data, format="json", **self.header)
|
|
452
|
+
self.assertEqual(response.status_code, 400)
|
|
453
|
+
self.assertIn(b"Related object not found using the provided attribute", response.content)
|
|
454
|
+
|
|
455
|
+
# Edit a permitted object with related object permissions
|
|
456
|
+
related_obj_perm.users.add(self.user)
|
|
457
|
+
response = self.client.patch(url, data, format="json", **self.header)
|
|
425
458
|
self.assertEqual(response.status_code, 200)
|
|
426
459
|
|
|
427
460
|
# Attempt to modify a permitted object to a non-permitted object
|
|
@@ -456,6 +489,32 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
456
489
|
response = self.client.delete(url, format="json", **self.header)
|
|
457
490
|
self.assertEqual(response.status_code, 204)
|
|
458
491
|
|
|
492
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
493
|
+
def test_related_object_permission_constraints_on_get_requests(self):
|
|
494
|
+
"""
|
|
495
|
+
Users who have permission to view Location objects, but not LocationType and Status objects
|
|
496
|
+
should still be able to view Location objects from the API.
|
|
497
|
+
"""
|
|
498
|
+
self.add_permissions("dcim.view_location")
|
|
499
|
+
response = self.client.get(reverse("dcim-api:location-list"), **self.header)
|
|
500
|
+
self.assertEqual(response.status_code, 200)
|
|
501
|
+
# we should be able to get all the locations
|
|
502
|
+
self.assertEqual(len(response.data["results"]), Location.objects.count())
|
|
503
|
+
|
|
504
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
505
|
+
def test_related_object_permission_constraints_on_patch_requests(self):
|
|
506
|
+
"""
|
|
507
|
+
Users who have permission to view and change Location objects, but not LocationType and Status objects
|
|
508
|
+
should still be able to change a Location object's name from the API.
|
|
509
|
+
"""
|
|
510
|
+
self.add_permissions("dcim.view_location", "dcim.change_location")
|
|
511
|
+
location = Location.objects.first()
|
|
512
|
+
data = {"name": "New Location Name"}
|
|
513
|
+
url = reverse("dcim-api:location-detail", kwargs={"pk": location.pk})
|
|
514
|
+
response = self.client.patch(url, data, format="json", **self.header)
|
|
515
|
+
self.assertEqual(response.status_code, 200)
|
|
516
|
+
self.assertEqual(response.data["name"], "New Location Name")
|
|
517
|
+
|
|
459
518
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
460
519
|
def test_user_token_constraints(self):
|
|
461
520
|
"""
|
|
@@ -487,6 +546,16 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
487
546
|
)
|
|
488
547
|
obj_perm.users.add(self.user, obj_user2)
|
|
489
548
|
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
|
|
549
|
+
related_obj_perm = ObjectPermission.objects.create(
|
|
550
|
+
name="Related object permission",
|
|
551
|
+
actions=["view"],
|
|
552
|
+
)
|
|
553
|
+
related_obj_perm.users.add(self.user, obj_user2)
|
|
554
|
+
related_obj_perm.object_types.add(
|
|
555
|
+
ContentType.objects.get_for_model(Namespace),
|
|
556
|
+
ContentType.objects.get_for_model(Status),
|
|
557
|
+
ContentType.objects.get_for_model(Location),
|
|
558
|
+
)
|
|
490
559
|
# Create one Prefix object per user
|
|
491
560
|
self.client.post(url, data[0], format="json", **self.header)
|
|
492
561
|
self.client.post(url, data[1], format="json", **header_user2)
|
|
@@ -550,6 +619,16 @@ class ObjectPermissionAPIViewTestCase(TestCase):
|
|
|
550
619
|
)
|
|
551
620
|
obj_perm.users.add(self.user, obj_user2)
|
|
552
621
|
obj_perm.object_types.add(ContentType.objects.get_for_model(Prefix))
|
|
622
|
+
related_obj_perm = ObjectPermission.objects.create(
|
|
623
|
+
name="Related object permission",
|
|
624
|
+
actions=["view"],
|
|
625
|
+
)
|
|
626
|
+
related_obj_perm.users.add(self.user, obj_user2)
|
|
627
|
+
related_obj_perm.object_types.add(
|
|
628
|
+
ContentType.objects.get_for_model(Namespace),
|
|
629
|
+
ContentType.objects.get_for_model(Status),
|
|
630
|
+
ContentType.objects.get_for_model(Location),
|
|
631
|
+
)
|
|
553
632
|
# Create one Prefix object per user
|
|
554
633
|
self.client.post(url, data[0], format="json", **self.header)
|
|
555
634
|
self.client.post(url, data[1], format="json", **header_user2)
|
|
@@ -486,20 +486,23 @@ class NumericArrayFieldTest(TestCase):
|
|
|
486
486
|
self.field = dcim_forms.DeviceFilterForm().fields["device_redundancy_group_priority"]
|
|
487
487
|
|
|
488
488
|
def test_valid_input(self):
|
|
489
|
-
#
|
|
490
|
-
tests =
|
|
491
|
-
None
|
|
492
|
-
""
|
|
493
|
-
"80,443-444"
|
|
494
|
-
"1024-1028,31337"
|
|
495
|
-
|
|
496
|
-
|
|
489
|
+
# List of (input, expected output) tuples
|
|
490
|
+
tests = [
|
|
491
|
+
(None, []),
|
|
492
|
+
("", []),
|
|
493
|
+
("80,443-444", [80, 443, 444]),
|
|
494
|
+
("1024-1028,31337", [1024, 1025, 1026, 1027, 1028, 31337]),
|
|
495
|
+
(["47-49", "103"], [47, 48, 49, 103]),
|
|
496
|
+
([231, 432, 313], [231, 313, 432]),
|
|
497
|
+
]
|
|
498
|
+
for test, expected in tests:
|
|
497
499
|
self.assertEqual(self.field.clean(test), expected)
|
|
498
500
|
|
|
499
501
|
def test_invalid_input(self):
|
|
500
502
|
tests = [
|
|
501
503
|
"pizza",
|
|
502
504
|
"-41",
|
|
505
|
+
"[84,52,33]",
|
|
503
506
|
]
|
|
504
507
|
for test in tests:
|
|
505
508
|
with self.assertRaises(django_forms.ValidationError):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import datetime
|
|
2
3
|
import random
|
|
3
4
|
import types
|
|
@@ -8,6 +9,7 @@ from django.apps import apps
|
|
|
8
9
|
from django.contrib.auth import get_user_model
|
|
9
10
|
from django.contrib.auth.models import Group
|
|
10
11
|
from django.contrib.contenttypes.models import ContentType
|
|
12
|
+
from django.core.cache import cache
|
|
11
13
|
from django.db.models import Count, Q
|
|
12
14
|
from django.test import override_settings, TestCase
|
|
13
15
|
from django.test.client import RequestFactory
|
|
@@ -17,6 +19,7 @@ from graphene_django.registry import get_global_registry
|
|
|
17
19
|
from graphene_django.settings import graphene_settings
|
|
18
20
|
from graphql import get_default_backend, GraphQLError
|
|
19
21
|
from graphql.error.located_error import GraphQLLocatedError
|
|
22
|
+
import redis.exceptions
|
|
20
23
|
from rest_framework import status
|
|
21
24
|
|
|
22
25
|
from nautobot.circuits.models import CircuitTermination, Provider
|
|
@@ -396,6 +399,12 @@ class GraphQLExtendSchemaRelationship(GraphQLTestCaseBase):
|
|
|
396
399
|
self.location_schema = generate_schema_type(app_name="dcim", model=Location)
|
|
397
400
|
self.vlan_schema = generate_schema_type(app_name="ipam", model=VLAN)
|
|
398
401
|
|
|
402
|
+
def tearDown(self):
|
|
403
|
+
"""Ensure that relationship caches are cleared to avoid leakage into other tests."""
|
|
404
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
405
|
+
cache.delete_pattern(f"{Relationship.objects.get_for_model_source.cache_key_prefix}.*")
|
|
406
|
+
cache.delete_pattern(f"{Relationship.objects.get_for_model_destination.cache_key_prefix}.*")
|
|
407
|
+
|
|
399
408
|
def test_extend_relationship_default_prefix(self):
|
|
400
409
|
"""Verify that relationships are correctly added to the schema."""
|
|
401
410
|
schema = extend_schema_type_relationships(self.vlan_schema, VLAN)
|
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -353,6 +353,13 @@ class ImportObjectsTestCase(TransactionTestCase):
|
|
|
353
353
|
self.assertEqual(log_successes[4].message, "Created 4 status object(s) from 5 row(s) of data")
|
|
354
354
|
|
|
355
355
|
def test_csv_import_contact_assignment(self):
|
|
356
|
+
self.add_permissions(
|
|
357
|
+
"dcim.view_locationtype",
|
|
358
|
+
"extras.view_status",
|
|
359
|
+
"dcim.view_location",
|
|
360
|
+
"extras.add_role",
|
|
361
|
+
"extras.add_contact",
|
|
362
|
+
)
|
|
356
363
|
location_types_csv = "\n".join(["name", "ContactAssignmentImportTestLocationType"])
|
|
357
364
|
locations_csv = "\n".join(
|
|
358
365
|
[
|
|
@@ -8,7 +8,7 @@ import logging
|
|
|
8
8
|
from django.contrib.contenttypes.models import ContentType
|
|
9
9
|
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
|
10
10
|
from django.db import models
|
|
11
|
-
from django.db.models import CharField, JSONField, URLField
|
|
11
|
+
from django.db.models import CharField, JSONField, Q, URLField
|
|
12
12
|
from django.db.models.fields.related import ManyToManyField
|
|
13
13
|
from django.template import Context
|
|
14
14
|
from django.template.defaultfilters import truncatechars
|
|
@@ -375,6 +375,11 @@ class Tab(Component):
|
|
|
375
375
|
class DistinctViewTab(Tab):
|
|
376
376
|
"""
|
|
377
377
|
A Tab that doesn't render inline on the same page, but instead links to a distinct view of its own when clicked.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
url_name (str): The name of the URL pattern to link to, which will be reversed to generate the URL.
|
|
381
|
+
label_wrapper_template_path (str, optional): Template path to render the tab label to HTML.
|
|
382
|
+
related_object_attribute (str, optional): The name of the related object attribute to count for the tab label.
|
|
378
383
|
"""
|
|
379
384
|
|
|
380
385
|
def __init__(
|
|
@@ -382,9 +387,11 @@ class DistinctViewTab(Tab):
|
|
|
382
387
|
*,
|
|
383
388
|
url_name,
|
|
384
389
|
label_wrapper_template_path="components/tab/label_wrapper_distinct_view.html",
|
|
390
|
+
related_object_attribute="",
|
|
385
391
|
**kwargs,
|
|
386
392
|
):
|
|
387
393
|
self.url_name = url_name
|
|
394
|
+
self.related_object_attribute = related_object_attribute
|
|
388
395
|
super().__init__(label_wrapper_template_path=label_wrapper_template_path, **kwargs)
|
|
389
396
|
|
|
390
397
|
def get_extra_context(self, context: Context):
|
|
@@ -393,6 +400,30 @@ class DistinctViewTab(Tab):
|
|
|
393
400
|
def render(self, context: Context):
|
|
394
401
|
return ""
|
|
395
402
|
|
|
403
|
+
def render_label(self, context: Context):
|
|
404
|
+
if not self.related_object_attribute:
|
|
405
|
+
return super().render_label(context)
|
|
406
|
+
|
|
407
|
+
obj = get_obj_from_context(context)
|
|
408
|
+
if not hasattr(obj, self.related_object_attribute):
|
|
409
|
+
logger.warning(
|
|
410
|
+
f"{obj} does not have a related attribute {self.related_object_attribute} to count for tab label."
|
|
411
|
+
)
|
|
412
|
+
return super().render_label(context)
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
related_obj_count = getattr(obj, self.related_object_attribute).count()
|
|
416
|
+
return format_html(
|
|
417
|
+
"{} {}",
|
|
418
|
+
self.label,
|
|
419
|
+
render_to_string("utilities/templatetags/badge.html", badge(related_obj_count)),
|
|
420
|
+
)
|
|
421
|
+
except AttributeError:
|
|
422
|
+
logger.warning(
|
|
423
|
+
f"{obj}'s attribute {self.related_object_attribute} is not a related manager to count for tab label."
|
|
424
|
+
)
|
|
425
|
+
return super().render_label(context)
|
|
426
|
+
|
|
396
427
|
|
|
397
428
|
class Panel(Component):
|
|
398
429
|
"""Base class for defining an individual display panel within a Layout within a Tab."""
|
|
@@ -634,10 +665,14 @@ class ObjectsTablePanel(Panel):
|
|
|
634
665
|
Table (`BaseTable`) instance. Mutually exclusive with `table_class`, `table_filter`, `table_attribute`.
|
|
635
666
|
table_class (obj): The table class that will be instantiated and rendered e.g. CircuitTable, DeviceTable.
|
|
636
667
|
Mutually exclusive with `context_table_key`.
|
|
637
|
-
table_filter (str, optional): The
|
|
668
|
+
table_filter (str, list, optional): The filter(s) to apply to the queryset to initialize the table class.
|
|
638
669
|
For example, in a LocationType detail view, for an ObjectsTablePanel of related Locations, this would
|
|
639
670
|
be `location_type`, because `Location.objects.filter(location_type=obj)` gives the desired queryset.
|
|
640
671
|
Mutually exclusive with `table_attribute`.
|
|
672
|
+
For example, in ProviderNetwork detail view, for an ObjectsTablePanel of related Circuits, this would
|
|
673
|
+
be `["circuit_termination_a__provider_network", "circuit_termination_z__provider_network"]` because
|
|
674
|
+
`Circuit.objects.filter(Q(circuit_termination_a__provider_network=instance)
|
|
675
|
+
| Q(circuit_termination_z__provider_network=instance))` gives the desired queryset.
|
|
641
676
|
table_attribute (str, optional): The attribute of the detail view instance that contains the queryset to
|
|
642
677
|
initialize the table class. e.g. `dynamic_groups`.
|
|
643
678
|
Mutually exclusive with `table_filter`.
|
|
@@ -758,7 +793,16 @@ class ObjectsTablePanel(Panel):
|
|
|
758
793
|
if self.table_attribute:
|
|
759
794
|
body_content_table_queryset = getattr(instance, self.table_attribute)
|
|
760
795
|
else:
|
|
761
|
-
|
|
796
|
+
if isinstance(self.table_filter, str):
|
|
797
|
+
table_filters = [self.table_filter]
|
|
798
|
+
elif isinstance(self.table_filter, list):
|
|
799
|
+
table_filters = self.table_filter
|
|
800
|
+
else:
|
|
801
|
+
table_filters = []
|
|
802
|
+
query = Q()
|
|
803
|
+
for table_filter in table_filters:
|
|
804
|
+
query = query | Q(**{table_filter: instance})
|
|
805
|
+
body_content_table_queryset = body_content_table_model.objects.filter(query)
|
|
762
806
|
|
|
763
807
|
body_content_table_queryset = body_content_table_queryset.restrict(request.user, "view")
|
|
764
808
|
if self.select_related_fields:
|