nautobot 2.4.16__py3-none-any.whl → 2.4.18__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/utils.py +2 -0
- nautobot/apps/views.py +2 -0
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
- nautobot/circuits/tests/integration/test_circuit.py +2 -2
- nautobot/circuits/views.py +32 -15
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
- nautobot/cloud/views.py +7 -0
- nautobot/core/apps/__init__.py +1 -0
- nautobot/core/celery/__init__.py +2 -1
- nautobot/core/filters.py +2 -2
- nautobot/core/settings.py +1 -0
- nautobot/core/settings.yaml +9 -0
- nautobot/core/tables.py +21 -23
- nautobot/core/templates/components/breadcrumbs.html +19 -0
- nautobot/core/templates/components/panel/panel.html +1 -1
- nautobot/core/templates/generic/object_changelog.html +0 -2
- nautobot/core/templates/generic/object_list.html +15 -12
- nautobot/core/templates/generic/object_notes.html +0 -2
- nautobot/core/templates/generic/object_retrieve.html +16 -9
- nautobot/core/templates/inc/paginator.html +3 -3
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templatetags/helpers.py +104 -6
- nautobot/core/templatetags/ui_framework.py +40 -5
- nautobot/core/testing/filters.py +37 -21
- nautobot/core/testing/mixins.py +1 -1
- nautobot/core/testing/views.py +27 -4
- nautobot/core/tests/test_tables.py +43 -6
- nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
- nautobot/core/tests/test_titles.py +2 -2
- nautobot/core/tests/test_ui.py +14 -1
- nautobot/core/tests/test_views.py +45 -0
- nautobot/core/ui/breadcrumbs.py +13 -8
- nautobot/core/ui/bulk_buttons.py +53 -53
- nautobot/core/ui/object_detail.py +52 -9
- nautobot/core/ui/titles.py +9 -5
- nautobot/core/utils/data.py +13 -0
- nautobot/core/utils/deprecation.py +2 -0
- nautobot/core/views/__init__.py +24 -3
- nautobot/core/views/generic.py +42 -17
- nautobot/core/views/mixins.py +146 -12
- nautobot/core/views/utils.py +117 -0
- nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
- nautobot/dcim/models/device_component_templates.py +4 -2
- nautobot/dcim/models/device_components.py +3 -2
- nautobot/dcim/models/devices.py +4 -0
- nautobot/dcim/tables/__init__.py +2 -0
- nautobot/dcim/tables/devices.py +24 -0
- nautobot/dcim/tables/power.py +2 -2
- nautobot/dcim/templates/dcim/device/base.html +1 -11
- nautobot/dcim/templates/dcim/device_component.html +0 -19
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -16
- nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
- nautobot/dcim/tests/test_views.py +41 -0
- nautobot/dcim/views.py +169 -39
- nautobot/extras/filters/mixins.py +1 -1
- nautobot/extras/forms/forms.py +15 -0
- nautobot/extras/models/customfields.py +45 -9
- nautobot/extras/models/groups.py +10 -1
- nautobot/extras/models/jobs.py +2 -2
- nautobot/extras/plugins/views.py +18 -5
- nautobot/extras/tables.py +4 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
- nautobot/extras/templates/extras/configcontext_update.html +49 -49
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
- nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
- nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
- nautobot/extras/templates/extras/dynamicgroup.html +2 -99
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
- nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
- nautobot/extras/templates/extras/gitrepository.html +2 -82
- nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
- nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
- nautobot/extras/templates/extras/gitrepository_update.html +13 -0
- nautobot/extras/templates/extras/inc/job_table.html +1 -1
- nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
- nautobot/extras/templates/extras/note_retrieve.html +1 -53
- nautobot/extras/templates/extras/plugin_detail.html +3 -7
- nautobot/extras/templates/extras/plugins_list.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +14 -14
- nautobot/extras/templates/extras/team_retrieve.html +1 -1
- nautobot/extras/tests/test_dynamicgroups.py +73 -18
- nautobot/extras/tests/test_models.py +216 -0
- nautobot/extras/tests/test_views.py +7 -2
- nautobot/extras/urls.py +2 -94
- nautobot/extras/views.py +425 -430
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/jobs/__init__.py +10 -0
- nautobot/ipam/jobs/cleanup.py +296 -0
- nautobot/ipam/models.py +301 -178
- nautobot/ipam/querysets.py +3 -3
- nautobot/ipam/signals.py +6 -1
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
- nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
- nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -8
- nautobot/ipam/templates/ipam/prefix_list.html +1 -1
- nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
- nautobot/ipam/tests/test_api.py +5 -0
- nautobot/ipam/tests/test_jobs.py +454 -0
- nautobot/ipam/tests/test_models.py +677 -122
- nautobot/ipam/tests/test_querysets.py +46 -0
- nautobot/ipam/tests/test_views.py +40 -164
- nautobot/ipam/urls.py +0 -11
- nautobot/ipam/utils/migrations.py +1 -1
- nautobot/ipam/utils/testing.py +9 -4
- nautobot/ipam/views.py +175 -235
- nautobot/project-static/docs/404.html +9 -6
- nautobot/project-static/docs/apps/index.html +9 -6
- nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
- nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +81 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +73 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +402 -21
- nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
- nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
- nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
- nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -9
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
- nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
- nautobot/project-static/docs/development/apps/api/setup.html +9 -6
- nautobot/project-static/docs/development/apps/api/testing.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
- nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
- nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
- nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
- nautobot/project-static/docs/development/apps/index.html +9 -6
- nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
- nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
- nautobot/project-static/docs/development/core/application-registry.html +23 -20
- nautobot/project-static/docs/development/core/best-practices.html +23 -20
- nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
- nautobot/project-static/docs/development/core/caching.html +9 -6
- nautobot/project-static/docs/development/core/controllers.html +9 -6
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
- nautobot/project-static/docs/development/core/generic-views.html +9 -6
- nautobot/project-static/docs/development/core/getting-started.html +9 -21
- nautobot/project-static/docs/development/core/homepage.html +12 -9
- nautobot/project-static/docs/development/core/index.html +9 -6
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
- nautobot/project-static/docs/development/core/model-checklist.html +9 -6
- nautobot/project-static/docs/development/core/model-features.html +11 -8
- nautobot/project-static/docs/development/core/natural-keys.html +21 -18
- nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
- nautobot/project-static/docs/development/core/release-checklist.html +9 -6
- nautobot/project-static/docs/development/core/role-internals.html +9 -6
- nautobot/project-static/docs/development/core/settings.html +9 -6
- nautobot/project-static/docs/development/core/style-guide.html +32 -29
- nautobot/project-static/docs/development/core/templates.html +9 -6
- nautobot/project-static/docs/development/core/testing.html +10 -7
- nautobot/project-static/docs/development/core/ui-component-framework.html +42 -44
- nautobot/project-static/docs/development/core/user-preferences.html +9 -6
- nautobot/project-static/docs/development/index.html +9 -6
- nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
- nautobot/project-static/docs/development/jobs/index.html +9 -6
- nautobot/project-static/docs/development/jobs/installation.html +23 -20
- nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
- nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
- nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
- nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
- nautobot/project-static/docs/development/jobs/testing.html +14 -11
- nautobot/project-static/docs/index.html +9 -6
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +9 -6
- nautobot/project-static/docs/overview/design_philosophy.html +9 -6
- nautobot/project-static/docs/release-notes/index.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
- nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
- nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.4.html +489 -6
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +301 -301
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
- nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
- nautobot/project-static/docs/user-guide/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
- nautobot/project-static/fonts/UFL.txt +96 -96
- nautobot/project-static/img/nautobot_icon.svg +32 -34
- nautobot/project-static/js/forms.js +35 -2
- nautobot/project-static/js/table_sorting_indicator.js +0 -2
- nautobot/virtualization/filters.py +7 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/METADATA +8 -8
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/RECORD +431 -421
- nautobot/core/templates/inc/breadcrumbs.html +0 -14
- nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
- nautobot/project-static/docs/requirements.txt +0 -14
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/NOTICE +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/WHEEL +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/entry_points.txt +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
import datetime
|
|
2
3
|
import json
|
|
3
4
|
import logging
|
|
@@ -23,7 +24,7 @@ import yaml
|
|
|
23
24
|
from nautobot.apps.config import get_app_settings_or_config
|
|
24
25
|
from nautobot.core import forms
|
|
25
26
|
from nautobot.core.constants import PAGINATE_COUNT_DEFAULT
|
|
26
|
-
from nautobot.core.utils import color, config, data, logging as nautobot_logging, lookup
|
|
27
|
+
from nautobot.core.utils import color, config, data, deprecation, logging as nautobot_logging, lookup
|
|
27
28
|
from nautobot.core.utils.requests import add_nautobot_version_query_param_to_url
|
|
28
29
|
|
|
29
30
|
HTML_TRUE = mark_safe('<span class="text-success"><i class="mdi mdi-check-bold" title="Yes"></i></span>')
|
|
@@ -762,6 +763,23 @@ def render_address(address):
|
|
|
762
763
|
return HTML_NONE
|
|
763
764
|
|
|
764
765
|
|
|
766
|
+
@register.filter()
|
|
767
|
+
def render_m2m(queryset, full_listing_link, verbose_name_plural, max_visible=5):
|
|
768
|
+
total_count = queryset.count()
|
|
769
|
+
display_count = min(total_count, max_visible)
|
|
770
|
+
if not display_count:
|
|
771
|
+
return HTML_NONE
|
|
772
|
+
|
|
773
|
+
items = [hyperlinked_object(record) for record in queryset[:display_count]]
|
|
774
|
+
|
|
775
|
+
remaining = total_count - display_count
|
|
776
|
+
if remaining > 0:
|
|
777
|
+
link = format_html('<a href="{}">... View {} more {}</a>', full_listing_link, remaining, verbose_name_plural)
|
|
778
|
+
items.append(link)
|
|
779
|
+
|
|
780
|
+
return format_html_join("", "<div>{}</div>", ((item,) for item in items)) if items else HTML_NONE
|
|
781
|
+
|
|
782
|
+
|
|
765
783
|
@library.filter()
|
|
766
784
|
@register.filter()
|
|
767
785
|
def render_button_class(value):
|
|
@@ -823,11 +841,7 @@ def get_attr(obj, attr, default=None):
|
|
|
823
841
|
return getattr(obj, attr, default)
|
|
824
842
|
|
|
825
843
|
|
|
826
|
-
|
|
827
|
-
def querystring(request, **kwargs):
|
|
828
|
-
"""
|
|
829
|
-
Append or update the page number in a querystring.
|
|
830
|
-
"""
|
|
844
|
+
def _base_querystring(request, **kwargs):
|
|
831
845
|
querydict = request.GET.copy()
|
|
832
846
|
for k, v in kwargs.items():
|
|
833
847
|
if v is not None:
|
|
@@ -841,6 +855,66 @@ def querystring(request, **kwargs):
|
|
|
841
855
|
return ""
|
|
842
856
|
|
|
843
857
|
|
|
858
|
+
# TODO: Remove this tag in Nautobot 3.0.
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
@register.simple_tag()
|
|
862
|
+
@deprecation.method_deprecated(
|
|
863
|
+
"Leverage `legacy_querystring` instead of `querystring` if this templatetag is required. In Nautobot 3.0, "
|
|
864
|
+
"`querystring` will be removed in preparation for Django 5.2 in which there is a built-in querystring tag "
|
|
865
|
+
"that operates differently. You may find that `django_querystring` is more appropriate for your use case "
|
|
866
|
+
"and is a replica of Django 5.2's `querystring` templatetag."
|
|
867
|
+
)
|
|
868
|
+
def querystring(request, **kwargs):
|
|
869
|
+
return _base_querystring(request, **kwargs)
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
@register.simple_tag()
|
|
873
|
+
def legacy_querystring(request, **kwargs):
|
|
874
|
+
return _base_querystring(request, **kwargs)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
# Note: This is vendored from Django 5.2
|
|
878
|
+
@register.simple_tag(name="django_querystring", takes_context=True)
|
|
879
|
+
def django_querystring(context, query_dict=None, **kwargs):
|
|
880
|
+
"""
|
|
881
|
+
Add, remove, and change parameters of a ``QueryDict`` and return the result
|
|
882
|
+
as a query string. If the ``query_dict`` argument is not provided, default
|
|
883
|
+
to ``request.GET``.
|
|
884
|
+
|
|
885
|
+
For example::
|
|
886
|
+
|
|
887
|
+
{% django_querystring foo=3 %}
|
|
888
|
+
|
|
889
|
+
To remove a key::
|
|
890
|
+
|
|
891
|
+
{% django_querystring foo=None %}
|
|
892
|
+
|
|
893
|
+
To use with pagination::
|
|
894
|
+
|
|
895
|
+
{% django_querystring page=page_obj.next_page_number %}
|
|
896
|
+
|
|
897
|
+
A custom ``QueryDict`` can also be used::
|
|
898
|
+
|
|
899
|
+
{% django_querystring my_query_dict foo=3 %}
|
|
900
|
+
"""
|
|
901
|
+
if query_dict is None:
|
|
902
|
+
query_dict = context.request.GET
|
|
903
|
+
params = query_dict.copy()
|
|
904
|
+
for key, value in kwargs.items():
|
|
905
|
+
if value is None:
|
|
906
|
+
if key in params:
|
|
907
|
+
del params[key]
|
|
908
|
+
elif isinstance(value, Iterable) and not isinstance(value, str):
|
|
909
|
+
params.setlist(key, value)
|
|
910
|
+
else:
|
|
911
|
+
params[key] = value
|
|
912
|
+
if not params and not query_dict:
|
|
913
|
+
return ""
|
|
914
|
+
query_string = params.urlencode()
|
|
915
|
+
return f"?{query_string}"
|
|
916
|
+
|
|
917
|
+
|
|
844
918
|
@register.simple_tag()
|
|
845
919
|
def table_config_button(table, table_name=None, extra_classes="", disabled=False):
|
|
846
920
|
if table_name is None:
|
|
@@ -1278,3 +1352,27 @@ def saved_view_title(context, mode: Literal["html", "plain"] = "html"):
|
|
|
1278
1352
|
return strip_tags(title)
|
|
1279
1353
|
|
|
1280
1354
|
return title
|
|
1355
|
+
|
|
1356
|
+
|
|
1357
|
+
# https://www.djangosnippets.org/snippets/545/
|
|
1358
|
+
@register.tag(name="captureas")
|
|
1359
|
+
def do_captureas(parser, token):
|
|
1360
|
+
try:
|
|
1361
|
+
_, args = token.contents.split(None, 1)
|
|
1362
|
+
except ValueError:
|
|
1363
|
+
raise template.TemplateSyntaxError("'captureas' node requires a variable name.")
|
|
1364
|
+
nodelist = parser.parse(("endcaptureas",))
|
|
1365
|
+
parser.delete_first_token()
|
|
1366
|
+
return CaptureasNode(nodelist, args)
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
class CaptureasNode(template.Node):
|
|
1370
|
+
def __init__(self, nodelist, varname):
|
|
1371
|
+
self.nodelist = nodelist
|
|
1372
|
+
self.varname = varname
|
|
1373
|
+
|
|
1374
|
+
def render(self, context):
|
|
1375
|
+
output = self.nodelist.render(context)
|
|
1376
|
+
output = output.strip()
|
|
1377
|
+
context[self.varname] = output
|
|
1378
|
+
return ""
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from functools import partial
|
|
1
2
|
import logging
|
|
2
3
|
|
|
3
4
|
from django import template
|
|
4
|
-
from django.utils.html import format_html_join
|
|
5
|
+
from django.utils.html import format_html_join, strip_spaces_between_tags
|
|
5
6
|
|
|
6
7
|
from nautobot.core.ui.breadcrumbs import Breadcrumbs
|
|
7
8
|
from nautobot.core.ui.titles import Titles
|
|
9
|
+
from nautobot.core.ui.utils import render_component_template
|
|
8
10
|
from nautobot.core.utils.lookup import get_view_for_model
|
|
9
11
|
from nautobot.core.views.utils import get_obj_from_context
|
|
10
12
|
|
|
@@ -31,21 +33,54 @@ def render_components(context, components):
|
|
|
31
33
|
|
|
32
34
|
@register.simple_tag(takes_context=True)
|
|
33
35
|
def render_title(context, mode="plain"):
|
|
36
|
+
"""
|
|
37
|
+
Render the title passed in the context. Due to backwards compatibility in most of the Generic views,
|
|
38
|
+
we're either passing `title` to the template or render `title` defined in `view_titles`.
|
|
39
|
+
|
|
40
|
+
But in some newer views we want to have simple way to render title, only by defining `view_titles` within a view class.
|
|
41
|
+
"""
|
|
42
|
+
if title := context.get("title"):
|
|
43
|
+
return title
|
|
44
|
+
|
|
34
45
|
title_obj = context.get("view_titles")
|
|
35
46
|
if title_obj is not None and isinstance(title_obj, Titles):
|
|
36
47
|
return title_obj.render(context, mode=mode)
|
|
37
48
|
|
|
38
|
-
if fallback_title := context.get("title"):
|
|
39
|
-
return fallback_title
|
|
40
49
|
return ""
|
|
41
50
|
|
|
42
51
|
|
|
43
52
|
@register.simple_tag(takes_context=True)
|
|
44
|
-
def render_breadcrumbs(context):
|
|
53
|
+
def render_breadcrumbs(context, legacy_default_breadcrumbs=None, legacy_block_breadcrumbs=None):
|
|
54
|
+
"""
|
|
55
|
+
Renders the breadcrumbs using the UI Component Framework or legacy template-defined breadcrumbs.
|
|
56
|
+
|
|
57
|
+
Function checks if breadcrumbs from UI Component Framework are available and render them but only
|
|
58
|
+
when there is no other changes coming from legacy template-defined breadcrumbs.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
- UI Component Framework breadcrumbs are defined in the view. But in the template, {% block breadcrumbs %} is being used,
|
|
62
|
+
to override breadcrumbs or `{% block extra_breadcrumbs %}`. Output: template breadcrumbs will be rendered.
|
|
63
|
+
- There is no UI Component Framework breadcrumbs and no other block overrides. Output: default breadcrumbs will be rendered.
|
|
64
|
+
- UI Component Framework breadcrumbs are defined in the view. No breadcrumbs block overrides. Output: UI Component Framework breadcrumbs will be rendered.
|
|
65
|
+
"""
|
|
66
|
+
render_template = partial(
|
|
67
|
+
render_component_template,
|
|
68
|
+
"components/breadcrumbs.html",
|
|
69
|
+
context,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
legacy_block_breadcrumbs
|
|
74
|
+
and strip_spaces_between_tags(legacy_default_breadcrumbs).strip()
|
|
75
|
+
!= strip_spaces_between_tags(legacy_block_breadcrumbs).strip()
|
|
76
|
+
):
|
|
77
|
+
return render_template(legacy_breadcrumbs=legacy_block_breadcrumbs)
|
|
78
|
+
|
|
45
79
|
breadcrumbs_obj = context.get("breadcrumbs")
|
|
46
80
|
if breadcrumbs_obj is not None and isinstance(breadcrumbs_obj, Breadcrumbs):
|
|
47
81
|
return breadcrumbs_obj.render(context)
|
|
48
|
-
|
|
82
|
+
|
|
83
|
+
return render_template(legacy_breadcrumbs=legacy_default_breadcrumbs)
|
|
49
84
|
|
|
50
85
|
|
|
51
86
|
@register.simple_tag(takes_context=True)
|
nautobot/core/testing/filters.py
CHANGED
|
@@ -99,6 +99,17 @@ class FilterTestCases:
|
|
|
99
99
|
self.assertIsNotNone(self.filterset)
|
|
100
100
|
return self.filterset.declared_filters["q"].filter_predicates
|
|
101
101
|
|
|
102
|
+
def test_no_distinct_on_empty_filter_params(self):
|
|
103
|
+
"""Verify that an empty filterset doesn't cause a `SELECT DISTINCT`."""
|
|
104
|
+
self.assertIsNotNone(self.filterset)
|
|
105
|
+
filterset = self.filterset({}, self.queryset) # pylint: disable=not-callable # see assertion above
|
|
106
|
+
self.assertTrue(filterset.is_valid())
|
|
107
|
+
self.assertNotIn(
|
|
108
|
+
"SELECT DISTINCT",
|
|
109
|
+
str(filterset.qs.query),
|
|
110
|
+
"Filter set with empty parameter added `DISTINCT` to select query. This needs to be avoided because it incurs heavy performance penalties.",
|
|
111
|
+
)
|
|
112
|
+
|
|
102
113
|
def test_id(self):
|
|
103
114
|
"""Verify that the filterset supports filtering by id with only lookup `__n`."""
|
|
104
115
|
self.assertIsNotNone(self.filterset)
|
|
@@ -326,29 +337,34 @@ class FilterTestCases:
|
|
|
326
337
|
if not isinstance(obj_field, (CharField, TextField)):
|
|
327
338
|
self.skipTest("Not a CharField or TextField")
|
|
328
339
|
|
|
340
|
+
original_value = getattr(obj, obj_field_name)
|
|
329
341
|
# Create random lowercase string to use for icontains lookup
|
|
330
342
|
max_length = obj_field.max_length or CHARFIELD_MAX_LENGTH
|
|
331
343
|
randomized_attr_value = "".join(random.choices(string.ascii_lowercase, k=max_length)) # noqa: S311 # pseudo-random generator
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
344
|
+
try:
|
|
345
|
+
setattr(obj, obj_field_name, randomized_attr_value)
|
|
346
|
+
obj.save()
|
|
347
|
+
|
|
348
|
+
# if lookup_method is iexact use the full updated attr
|
|
349
|
+
if lookup_method == "iexact":
|
|
350
|
+
lookup = randomized_attr_value.upper()
|
|
351
|
+
model_queryset = self.queryset.filter(**{f"{filter_field_name}__iexact": lookup})
|
|
352
|
+
else:
|
|
353
|
+
lookup = randomized_attr_value[1:].upper()
|
|
354
|
+
model_queryset = self.queryset.filter(**{f"{filter_field_name}__icontains": lookup})
|
|
355
|
+
params = {"q": lookup}
|
|
356
|
+
filterset_result = self.filterset(params, self.queryset) # pylint: disable=not-callable
|
|
357
|
+
|
|
358
|
+
self.assertTrue(filterset_result.is_valid())
|
|
359
|
+
self.assertQuerysetEqualAndNotEmpty(
|
|
360
|
+
filterset_result.qs,
|
|
361
|
+
model_queryset,
|
|
362
|
+
ordered=False,
|
|
363
|
+
msg=lookup,
|
|
364
|
+
)
|
|
365
|
+
finally:
|
|
366
|
+
setattr(obj, obj_field_name, original_value)
|
|
367
|
+
obj.save()
|
|
352
368
|
|
|
353
369
|
def _get_relevant_filterset_queryset(self, queryset, *filter_params):
|
|
354
370
|
"""Gets the relevant queryset based on filter parameters."""
|
|
@@ -454,7 +470,7 @@ class FilterTestCases:
|
|
|
454
470
|
tenant_groups = list(
|
|
455
471
|
models.TenantGroup.objects.filter(
|
|
456
472
|
tenants__isnull=False, **{f"tenants__{self.tenancy_related_name}__isnull": False}
|
|
457
|
-
)
|
|
473
|
+
).distinct()
|
|
458
474
|
)[:2]
|
|
459
475
|
tenant_groups_including_children = []
|
|
460
476
|
for tenant_group in tenant_groups:
|
nautobot/core/testing/mixins.py
CHANGED
|
@@ -156,7 +156,7 @@ class NautobotTestCaseMixin:
|
|
|
156
156
|
"""
|
|
157
157
|
for name in names:
|
|
158
158
|
ct, action = permissions.resolve_permission_ct(name)
|
|
159
|
-
obj_perm = users_models.ObjectPermission(name=name, actions=[action], **kwargs)
|
|
159
|
+
obj_perm, _ = users_models.ObjectPermission.objects.get_or_create(name=name, actions=[action], **kwargs)
|
|
160
160
|
obj_perm.save()
|
|
161
161
|
obj_perm.users.add(self.user)
|
|
162
162
|
obj_perm.object_types.add(ct)
|
nautobot/core/testing/views.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import inspect
|
|
2
3
|
import re
|
|
3
4
|
from typing import Optional, Sequence
|
|
4
5
|
from unittest import mock, skipIf
|
|
@@ -281,8 +282,7 @@ class ViewTestCases:
|
|
|
281
282
|
if getattr(obj, "is_contact_associable_model", False):
|
|
282
283
|
self.assertBodyContains(
|
|
283
284
|
response,
|
|
284
|
-
f'
|
|
285
|
-
html=True,
|
|
285
|
+
f'href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)"',
|
|
286
286
|
)
|
|
287
287
|
else:
|
|
288
288
|
self.assertNotContains(response, f"{obj.get_absolute_url()}#contacts")
|
|
@@ -304,8 +304,7 @@ class ViewTestCases:
|
|
|
304
304
|
if getattr(obj, "is_contact_associable_model", False):
|
|
305
305
|
self.assertBodyContains(
|
|
306
306
|
response,
|
|
307
|
-
f'
|
|
308
|
-
html=True,
|
|
307
|
+
f'href="{obj.get_absolute_url()}#contacts" onclick="switch_tab(this.href)"',
|
|
309
308
|
)
|
|
310
309
|
else:
|
|
311
310
|
self.assertNotContains(response, f"{obj.get_absolute_url()}#contacts")
|
|
@@ -778,6 +777,30 @@ class ViewTestCases:
|
|
|
778
777
|
response_body = response.content.decode(response.charset)
|
|
779
778
|
self.assertNotIn('<i class="mdi mdi-circle-small"></i>', response_body)
|
|
780
779
|
|
|
780
|
+
def test_model_properties_as_table_columns_are_not_orderable(self):
|
|
781
|
+
"""
|
|
782
|
+
Check for table columns that are property-based and not orderable.
|
|
783
|
+
"""
|
|
784
|
+
table_class = getattr(self.get_list_view(), "table_class", None)
|
|
785
|
+
if not table_class:
|
|
786
|
+
return
|
|
787
|
+
|
|
788
|
+
queryset = self._get_queryset()
|
|
789
|
+
table = table_class(queryset)
|
|
790
|
+
model_cls = table._meta.model
|
|
791
|
+
|
|
792
|
+
property_fields = {name for name, _ in inspect.getmembers(model_cls, lambda o: isinstance(o, property))}
|
|
793
|
+
|
|
794
|
+
for name, column in table.base_columns.items():
|
|
795
|
+
if hasattr(column, "order_by") and column.order_by:
|
|
796
|
+
continue
|
|
797
|
+
if name in property_fields and name != "pk":
|
|
798
|
+
with self.subTest(column_name=name):
|
|
799
|
+
self.assertFalse(
|
|
800
|
+
column.orderable,
|
|
801
|
+
f"On Table `{table_class.__name__}` the property-based column `{name}` should be orderable=False or use a custom order_by",
|
|
802
|
+
)
|
|
803
|
+
|
|
781
804
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
782
805
|
def test_list_objects_anonymous(self):
|
|
783
806
|
# Make the request as an unauthenticated user
|
|
@@ -18,8 +18,18 @@ class TableTestCase(TestCase):
|
|
|
18
18
|
def _validate_sorted_tree_queryset_same_with_table_queryset(self, queryset, table_class, field_name):
|
|
19
19
|
with self.subTest(f"Assert sorting {table_class.__name__} on '{field_name}'"):
|
|
20
20
|
table = table_class(queryset.with_tree_fields(), order_by=field_name)
|
|
21
|
-
table_queryset_data = table.data.data.values_list(
|
|
22
|
-
sorted_queryset =
|
|
21
|
+
table_queryset_data = table.data.data.values_list(field_name, flat=True)
|
|
22
|
+
sorted_queryset = (
|
|
23
|
+
queryset.with_tree_fields().extra(order_by=[field_name]).values_list(field_name, flat=True)
|
|
24
|
+
)
|
|
25
|
+
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
26
|
+
|
|
27
|
+
with self.subTest(f"Assert sorting {table_class.__name__} on '-{field_name}'"):
|
|
28
|
+
table = table_class(queryset.with_tree_fields(), order_by=f"-{field_name}")
|
|
29
|
+
table_queryset_data = table.data.data.values_list(field_name, flat=True)
|
|
30
|
+
sorted_queryset = (
|
|
31
|
+
queryset.with_tree_fields().extra(order_by=[f"-{field_name}"]).values_list(field_name, flat=True)
|
|
32
|
+
)
|
|
23
33
|
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
24
34
|
|
|
25
35
|
def test_tree_model_table_orderable(self):
|
|
@@ -61,14 +71,41 @@ class TableTestCase(TestCase):
|
|
|
61
71
|
table_avail_fields = set(model_field_names) & set(table_class.Meta.fields)
|
|
62
72
|
for table_field_name in table_avail_fields:
|
|
63
73
|
self._validate_sorted_tree_queryset_same_with_table_queryset(queryset, table_class, table_field_name)
|
|
64
|
-
self._validate_sorted_tree_queryset_same_with_table_queryset(
|
|
65
|
-
queryset, table_class, f"-{table_field_name}"
|
|
66
|
-
)
|
|
67
74
|
|
|
68
75
|
# Test for `rack_count`
|
|
69
76
|
queryset = RackGroupTable.Meta.model.objects.annotate(rack_count=count_related(Rack, "rack_group")).all()
|
|
70
77
|
self._validate_sorted_tree_queryset_same_with_table_queryset(queryset, RackGroupTable, "rack_count")
|
|
71
|
-
|
|
78
|
+
|
|
79
|
+
# https://github.com/nautobot/nautobot/issues/7330 - sorting by custom field
|
|
80
|
+
l1 = Location.objects.first()
|
|
81
|
+
l1._custom_field_data["example_app_auto_custom_field"] = "alpha"
|
|
82
|
+
l1.validated_save()
|
|
83
|
+
l2 = Location.objects.last()
|
|
84
|
+
l2._custom_field_data["example_app_auto_custom_field"] = "omega"
|
|
85
|
+
l2.validated_save()
|
|
86
|
+
table = LocationTable(
|
|
87
|
+
Location.objects.exclude(_custom_field_data__example_app_auto_custom_field="Default value")
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
table.order_by = ["cf_example_app_auto_custom_field"]
|
|
91
|
+
table_queryset_data = table.data.data.values_list("pk", flat=True)
|
|
92
|
+
sorted_queryset = (
|
|
93
|
+
Location.objects.with_tree_fields()
|
|
94
|
+
.exclude(_custom_field_data__example_app_auto_custom_field="Default value")
|
|
95
|
+
.extra(order_by=["_custom_field_data__example_app_auto_custom_field"])
|
|
96
|
+
.values_list("pk", flat=True)
|
|
97
|
+
)
|
|
98
|
+
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
99
|
+
|
|
100
|
+
table.order_by = ["-cf_example_app_auto_custom_field"]
|
|
101
|
+
table_queryset_data = table.data.data.values_list("pk", flat=True)
|
|
102
|
+
sorted_queryset = (
|
|
103
|
+
Location.objects.with_tree_fields()
|
|
104
|
+
.exclude(_custom_field_data__example_app_auto_custom_field="Default value")
|
|
105
|
+
.extra(order_by=["-_custom_field_data__example_app_auto_custom_field"])
|
|
106
|
+
.values_list("pk", flat=True)
|
|
107
|
+
)
|
|
108
|
+
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
72
109
|
|
|
73
110
|
def test_base_table_apis(self):
|
|
74
111
|
"""
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from django.template import Context
|
|
2
|
+
|
|
3
|
+
from nautobot.core.templatetags import ui_framework
|
|
4
|
+
from nautobot.core.testing import TestCase
|
|
5
|
+
from nautobot.core.ui.breadcrumbs import Breadcrumbs
|
|
6
|
+
from nautobot.core.ui.titles import Titles
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NautobotTemplatetagsUIComponentsTest(TestCase):
|
|
10
|
+
"""Tests template tags from ui_framework module."""
|
|
11
|
+
|
|
12
|
+
# ---------------------------
|
|
13
|
+
# render_title
|
|
14
|
+
# ---------------------------
|
|
15
|
+
|
|
16
|
+
def test_render_title_with_legacy_title_present(self):
|
|
17
|
+
context = Context(
|
|
18
|
+
{
|
|
19
|
+
"title": "Custom Title",
|
|
20
|
+
"view_titles": Titles(),
|
|
21
|
+
"verbose_name_plural": "Default Title",
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
output = ui_framework.render_title(context)
|
|
25
|
+
|
|
26
|
+
self.assertEqual(output, "Custom Title")
|
|
27
|
+
|
|
28
|
+
def test_render_title_with_view_titles_only(self):
|
|
29
|
+
context = Context(
|
|
30
|
+
{
|
|
31
|
+
"view_titles": Titles(),
|
|
32
|
+
"verbose_name_plural": "Default Title",
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
output = ui_framework.render_title(context)
|
|
36
|
+
|
|
37
|
+
self.assertEqual(output, "Default Title")
|
|
38
|
+
|
|
39
|
+
def test_render_title_with_invalid_view_titles(self):
|
|
40
|
+
class MyStuff:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
context = Context(
|
|
44
|
+
{
|
|
45
|
+
"view_titles": MyStuff(),
|
|
46
|
+
"verbose_name_plural": "Default Title",
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
output = ui_framework.render_title(context)
|
|
50
|
+
|
|
51
|
+
self.assertEqual(output, "")
|
|
52
|
+
|
|
53
|
+
def test_render_title_with_empty_context(self):
|
|
54
|
+
context = Context({})
|
|
55
|
+
output = ui_framework.render_title(context)
|
|
56
|
+
|
|
57
|
+
self.assertEqual(output, "")
|
|
58
|
+
|
|
59
|
+
# ---------------------------
|
|
60
|
+
# render_breadcrumbs
|
|
61
|
+
# ---------------------------
|
|
62
|
+
|
|
63
|
+
def test_render_breadcrumbs(self):
|
|
64
|
+
context = Context(
|
|
65
|
+
{
|
|
66
|
+
"list_url": "home",
|
|
67
|
+
"title": "New Home",
|
|
68
|
+
"view_action": "list",
|
|
69
|
+
"breadcrumbs": Breadcrumbs(),
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
output = ui_framework.render_breadcrumbs(context)
|
|
73
|
+
|
|
74
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">New Home</a></li></ol>')
|
|
75
|
+
|
|
76
|
+
def test_render_breadcrumbs_empty_context(self):
|
|
77
|
+
context = Context({})
|
|
78
|
+
output = ui_framework.render_breadcrumbs(context)
|
|
79
|
+
|
|
80
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"></ol>')
|
|
81
|
+
|
|
82
|
+
def test_render_breadcrumbs_with_legacy_breadcrumbs(self):
|
|
83
|
+
legacy_breadcrumbs = '<li><a href="/">Home</a></li>'
|
|
84
|
+
context = Context({})
|
|
85
|
+
output = ui_framework.render_breadcrumbs(context, legacy_breadcrumbs)
|
|
86
|
+
|
|
87
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">Home</a></li></ol>')
|
|
88
|
+
|
|
89
|
+
def test_render_breadcrumbs_with_legacy_and_block_breadcrumbs_the_same_with_breadcrumbs_class(self):
|
|
90
|
+
legacy_breadcrumbs = block_breadcrumbs = '<li><a href="/">Home</a></li>'
|
|
91
|
+
context = Context(
|
|
92
|
+
{
|
|
93
|
+
"list_url": "home",
|
|
94
|
+
"title": "New Home",
|
|
95
|
+
"view_action": "list",
|
|
96
|
+
"breadcrumbs": Breadcrumbs(),
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
output = ui_framework.render_breadcrumbs(context, legacy_breadcrumbs, block_breadcrumbs)
|
|
100
|
+
|
|
101
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">New Home</a></li></ol>')
|
|
102
|
+
|
|
103
|
+
def test_render_breadcrumbs_with_legacy_and_block_breadcrumbs_the_same_and_no_breadcrumbs_class(self):
|
|
104
|
+
legacy_breadcrumbs = block_breadcrumbs = '<li><a href="/">Home</a></li>'
|
|
105
|
+
context = Context({})
|
|
106
|
+
output = ui_framework.render_breadcrumbs(context, legacy_breadcrumbs, block_breadcrumbs)
|
|
107
|
+
|
|
108
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">Home</a></li></ol>')
|
|
109
|
+
|
|
110
|
+
def test_render_breadcrumbs_with_legacy_breadcrumbs_override(self):
|
|
111
|
+
legacy_breadcrumbs = '<li><a href="/">Home</a></li>'
|
|
112
|
+
block_breadcrumbs = '<li><a href="/">Override</a></li>'
|
|
113
|
+
context = Context(
|
|
114
|
+
{
|
|
115
|
+
"list_url": "home",
|
|
116
|
+
"title": "New Home",
|
|
117
|
+
"view_action": "list",
|
|
118
|
+
"breadcrumbs": Breadcrumbs(),
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
output = ui_framework.render_breadcrumbs(context, legacy_breadcrumbs, block_breadcrumbs)
|
|
122
|
+
|
|
123
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">Override</a></li></ol>')
|
|
124
|
+
|
|
125
|
+
def test_render_breadcrumbs_strips_tags(self):
|
|
126
|
+
legacy_breadcrumbs = """
|
|
127
|
+
<li>
|
|
128
|
+
<a href="/">Home</a>
|
|
129
|
+
</li>"""
|
|
130
|
+
|
|
131
|
+
block_breadcrumbs = """<li>
|
|
132
|
+
|
|
133
|
+
<a href="/">Home</a></li>
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
context = Context(
|
|
137
|
+
{
|
|
138
|
+
"list_url": "home",
|
|
139
|
+
"title": "New Home",
|
|
140
|
+
"view_action": "list",
|
|
141
|
+
"breadcrumbs": Breadcrumbs(),
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
output = ui_framework.render_breadcrumbs(context, legacy_breadcrumbs, block_breadcrumbs)
|
|
145
|
+
|
|
146
|
+
self.assertHTMLEqual(output, '<ol class="breadcrumb"><li><a href="/">New Home</a></li></ol>')
|
|
@@ -163,7 +163,7 @@ class TitlesTestCase(TestCase):
|
|
|
163
163
|
"""Test rendering when no view_action is provided."""
|
|
164
164
|
context = Context({"verbose_name_plural": "devices"})
|
|
165
165
|
result = self.titles.render(context)
|
|
166
|
-
self.assertEqual(result, "Devices") # Should use
|
|
166
|
+
self.assertEqual(result, "Devices") # Should use * action as default
|
|
167
167
|
|
|
168
168
|
def test_get_extra_context(self):
|
|
169
169
|
"""Test that get_extra_context returns empty dict by default."""
|
|
@@ -173,7 +173,7 @@ class TitlesTestCase(TestCase):
|
|
|
173
173
|
|
|
174
174
|
def test_get_extra_context_is_used_during_render(self):
|
|
175
175
|
"""Test that get_extra_context is being used to extend the context."""
|
|
176
|
-
context = Context({})
|
|
176
|
+
context = Context({"view_action": "list"})
|
|
177
177
|
|
|
178
178
|
class TitlesSubClass(Titles):
|
|
179
179
|
def get_extra_context(self, context: Context) -> dict:
|
nautobot/core/tests/test_ui.py
CHANGED
|
@@ -7,7 +7,7 @@ from django.test import RequestFactory
|
|
|
7
7
|
|
|
8
8
|
from nautobot.core.templatetags.helpers import HTML_NONE
|
|
9
9
|
from nautobot.core.testing import TestCase
|
|
10
|
-
from nautobot.core.ui.object_detail import BaseTextPanel, DataTablePanel, ObjectsTablePanel, Panel
|
|
10
|
+
from nautobot.core.ui.object_detail import BaseTextPanel, DataTablePanel, ObjectFieldsPanel, ObjectsTablePanel, Panel
|
|
11
11
|
from nautobot.dcim.models import DeviceRedundancyGroup
|
|
12
12
|
from nautobot.dcim.tables.devices import DeviceTable
|
|
13
13
|
|
|
@@ -68,6 +68,19 @@ class DataTablePanelTest(TestCase):
|
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
class ObjectFieldsPanelTest(TestCase):
|
|
72
|
+
def test_get_data_ignore_nonexistent_fields(self):
|
|
73
|
+
panel = ObjectFieldsPanel(weight=100, fields=["name", "foo", "bar"], ignore_nonexistent_fields=True)
|
|
74
|
+
redundancy_group = DeviceRedundancyGroup.objects.first()
|
|
75
|
+
context = Context({"object": redundancy_group})
|
|
76
|
+
data = panel.get_data(context)
|
|
77
|
+
self.assertEqual(data, {"name": redundancy_group.name}) # no keys for nonexistent fields
|
|
78
|
+
|
|
79
|
+
panel = ObjectFieldsPanel(weight=100, fields=["name", "foo", "bar"], ignore_nonexistent_fields=False)
|
|
80
|
+
with self.assertRaises(AttributeError):
|
|
81
|
+
data = panel.get_data(context)
|
|
82
|
+
|
|
83
|
+
|
|
71
84
|
class BaseTextPanelTest(TestCase):
|
|
72
85
|
def test_init_set_object_params(self):
|
|
73
86
|
# Test default settings
|