nautobot 2.2.7__py3-none-any.whl → 2.2.9__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/core/filters.py +15 -1
- nautobot/core/graphql/generators.py +4 -4
- nautobot/core/graphql/schema.py +9 -7
- nautobot/core/jobs/__init__.py +4 -1
- nautobot/core/settings.py +13 -2
- nautobot/core/settings.yaml +14 -0
- nautobot/core/templates/nautobot_config.py.j2 +15 -0
- nautobot/core/tests/integration/test_general_functionality.py +36 -0
- nautobot/core/tests/test_graphql.py +51 -5
- nautobot/core/tests/test_jobs.py +82 -2
- nautobot/core/tests/test_templatetags_netutils.py +3 -3
- nautobot/dcim/models/device_components.py +7 -0
- nautobot/dcim/models/devices.py +14 -3
- nautobot/dcim/tables/devices.py +19 -4
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/tests/test_models.py +106 -0
- nautobot/dcim/tests/test_views.py +13 -0
- nautobot/dcim/views.py +8 -2
- nautobot/extras/api/views.py +7 -59
- nautobot/extras/homepage.py +12 -2
- nautobot/extras/jobs.py +2 -2
- nautobot/extras/models/jobs.py +81 -0
- nautobot/extras/models/relationships.py +12 -0
- nautobot/extras/signals.py +14 -1
- nautobot/extras/tables.py +36 -5
- nautobot/extras/templates/extras/job_detail.html +11 -0
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/test_relationships.py +221 -1
- nautobot/extras/tests/test_views.py +21 -0
- nautobot/extras/utils.py +34 -5
- nautobot/extras/views.py +20 -46
- nautobot/ipam/models.py +9 -12
- nautobot/ipam/tests/test_models.py +3 -2
- nautobot/ipam/views.py +2 -8
- nautobot/project-static/css/base.css +1 -0
- nautobot/project-static/docs/404.html +4 -4
- nautobot/project-static/docs/apps/index.html +4 -4
- nautobot/project-static/docs/apps/nautobot-apps.html +4 -4
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css → main.76a95c52.min.css} +1 -1
- nautobot/project-static/docs/assets/stylesheets/{main.6543a935.min.css.map → main.76a95c52.min.css.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +4 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +6 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +4 -4
- nautobot/project-static/docs/development/apps/api/configuration-view.html +4 -4
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/global-search.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/graphql.html +4 -4
- nautobot/project-static/docs/development/apps/api/models/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +4 -4
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +4 -4
- nautobot/project-static/docs/development/apps/api/prometheus.html +4 -4
- nautobot/project-static/docs/development/apps/api/setup.html +4 -4
- nautobot/project-static/docs/development/apps/api/testing.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +4 -4
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/base-template.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/index.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +4 -4
- nautobot/project-static/docs/development/apps/api/views/urls.html +4 -4
- nautobot/project-static/docs/development/apps/index.html +4 -4
- nautobot/project-static/docs/development/apps/migration/code-updates.html +4 -4
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +4 -4
- nautobot/project-static/docs/development/apps/migration/from-v1.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +4 -4
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +4 -4
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +4 -4
- nautobot/project-static/docs/development/core/application-registry.html +4 -4
- nautobot/project-static/docs/development/core/best-practices.html +4 -4
- nautobot/project-static/docs/development/core/bootstrap-ui.html +4 -4
- nautobot/project-static/docs/development/core/caching.html +4 -4
- nautobot/project-static/docs/development/core/controllers.html +4 -4
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +4 -4
- nautobot/project-static/docs/development/core/generic-views.html +4 -4
- nautobot/project-static/docs/development/core/getting-started.html +4 -4
- nautobot/project-static/docs/development/core/homepage.html +4 -4
- nautobot/project-static/docs/development/core/index.html +15 -4
- nautobot/project-static/docs/development/core/model-checklist.html +4 -4
- nautobot/project-static/docs/development/core/model-features.html +4 -4
- nautobot/project-static/docs/development/core/natural-keys.html +4 -4
- nautobot/project-static/docs/development/core/navigation-menu.html +4 -4
- nautobot/project-static/docs/development/core/release-checklist.html +4 -4
- nautobot/project-static/docs/development/core/role-internals.html +4 -4
- nautobot/project-static/docs/development/core/settings.html +4 -4
- nautobot/project-static/docs/development/core/style-guide.html +4 -4
- nautobot/project-static/docs/development/core/templates.html +4 -4
- nautobot/project-static/docs/development/core/testing.html +4 -4
- nautobot/project-static/docs/development/core/user-preferences.html +4 -4
- nautobot/project-static/docs/development/index.html +4 -4
- nautobot/project-static/docs/development/jobs/index.html +379 -365
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +4 -4
- nautobot/project-static/docs/index.html +8228 -13
- nautobot/project-static/docs/overview/application_stack.html +4 -4
- nautobot/project-static/docs/overview/design_philosophy.html +6 -6
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.0.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.1.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.2.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.3.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.4.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.5.html +4 -4
- nautobot/project-static/docs/release-notes/version-1.6.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.0.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.1.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.2.html +419 -136
- nautobot/project-static/docs/requirements.txt +2 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +260 -260
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +36 -4
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +8 -4
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +8 -8
- nautobot/project-static/docs/user-guide/administration/installation/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -5
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation/services.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +4 -4
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +4 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +4 -4
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +4 -4
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +62 -10
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +4 -4
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +4 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +4 -4
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +4 -4
- nautobot/project-static/docs/user-guide/index.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +4 -4
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +4 -4
- nautobot/virtualization/tables.py +2 -5
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/METADATA +3 -3
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/RECORD +306 -305
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
- {nautobot-2.2.7.dist-info → nautobot-2.2.9.dist-info}/entry_points.txt +0 -0
|
@@ -16,6 +16,7 @@ from nautobot.dcim.choices import (
|
|
|
16
16
|
InterfaceTypeChoices,
|
|
17
17
|
PortTypeChoices,
|
|
18
18
|
PowerOutletFeedLegChoices,
|
|
19
|
+
SubdeviceRoleChoices,
|
|
19
20
|
)
|
|
20
21
|
from nautobot.dcim.models import (
|
|
21
22
|
Cable,
|
|
@@ -886,6 +887,13 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
886
887
|
self.device_type = DeviceType.objects.create(
|
|
887
888
|
manufacturer=manufacturer,
|
|
888
889
|
model="Test Device Type 1",
|
|
890
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_PARENT,
|
|
891
|
+
)
|
|
892
|
+
self.child_devicetype = DeviceType.objects.create(
|
|
893
|
+
model="Child Device Type 1",
|
|
894
|
+
manufacturer=manufacturer,
|
|
895
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
|
|
896
|
+
u_height=0,
|
|
889
897
|
)
|
|
890
898
|
self.device_role = Role.objects.get_for_model(Device).first()
|
|
891
899
|
self.device_status = Status.objects.get_for_model(Device).first()
|
|
@@ -1188,6 +1196,104 @@ class DeviceTestCase(ModelTestCases.BaseModelTestCase):
|
|
|
1188
1196
|
self.device_type.software_image_files.add(software_image_file)
|
|
1189
1197
|
self.device.validated_save()
|
|
1190
1198
|
|
|
1199
|
+
def test_child_devices_are_not_saved_when_unnecessary(self):
|
|
1200
|
+
parent_device = Device.objects.create(
|
|
1201
|
+
name="Parent Device 1",
|
|
1202
|
+
location=self.location_3,
|
|
1203
|
+
device_type=self.device_type,
|
|
1204
|
+
role=self.device_role,
|
|
1205
|
+
status=self.device_status,
|
|
1206
|
+
)
|
|
1207
|
+
parent_device.validated_save()
|
|
1208
|
+
|
|
1209
|
+
child_device = Device.objects.create(
|
|
1210
|
+
name="Child Device 1",
|
|
1211
|
+
location=parent_device.location,
|
|
1212
|
+
device_type=self.child_devicetype,
|
|
1213
|
+
role=parent_device.role,
|
|
1214
|
+
status=self.device_status,
|
|
1215
|
+
)
|
|
1216
|
+
child_device.validated_save()
|
|
1217
|
+
child_mtime_before_parent_saved = str(child_device.last_updated)
|
|
1218
|
+
|
|
1219
|
+
devicebay = DeviceBay.objects.get(device=parent_device, name="Device Bay 1")
|
|
1220
|
+
devicebay.installed_device = child_device
|
|
1221
|
+
devicebay.validated_save()
|
|
1222
|
+
|
|
1223
|
+
#
|
|
1224
|
+
# Tests
|
|
1225
|
+
#
|
|
1226
|
+
|
|
1227
|
+
#
|
|
1228
|
+
# On a NOOP save, the child device shouldn't be updated
|
|
1229
|
+
parent_device.save()
|
|
1230
|
+
|
|
1231
|
+
child_mtime_after_parent_noop_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1232
|
+
|
|
1233
|
+
self.assertEqual(child_mtime_before_parent_saved, child_mtime_after_parent_noop_save)
|
|
1234
|
+
|
|
1235
|
+
#
|
|
1236
|
+
# On a serial number update, the child device shouldn't be updated
|
|
1237
|
+
parent_device.serial = "12345"
|
|
1238
|
+
parent_device.save()
|
|
1239
|
+
|
|
1240
|
+
child_mtime_after_parent_serial_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1241
|
+
|
|
1242
|
+
self.assertEqual(child_mtime_before_parent_saved, child_mtime_after_parent_serial_update_save)
|
|
1243
|
+
|
|
1244
|
+
#
|
|
1245
|
+
# If the parent rack updates, the child mtime should update.
|
|
1246
|
+
rack = Rack.objects.create(name="Rack 1", location=parent_device.location, status=self.device_status)
|
|
1247
|
+
parent_device.rack = rack
|
|
1248
|
+
parent_device.save()
|
|
1249
|
+
|
|
1250
|
+
child_mtime_after_parent_rack_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1251
|
+
|
|
1252
|
+
self.assertNotEqual(child_mtime_after_parent_noop_save, child_mtime_after_parent_rack_update_save)
|
|
1253
|
+
|
|
1254
|
+
#
|
|
1255
|
+
# If the parent site updates, the child mtime should update
|
|
1256
|
+
location = Location.objects.create(
|
|
1257
|
+
name="New Site 1", status=self.device_status, location_type=self.location_type_3
|
|
1258
|
+
)
|
|
1259
|
+
parent_device.location = location
|
|
1260
|
+
parent_device.save()
|
|
1261
|
+
|
|
1262
|
+
child_mtime_after_parent_site_update_save = str(Device.objects.get(name="Child Device 1").last_updated)
|
|
1263
|
+
|
|
1264
|
+
self.assertNotEqual(child_mtime_after_parent_rack_update_save, child_mtime_after_parent_site_update_save)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
class DeviceBayTestCase(ModelTestCases.BaseModelTestCase):
|
|
1268
|
+
model = DeviceBay
|
|
1269
|
+
|
|
1270
|
+
def setUp(self):
|
|
1271
|
+
self.devices = Device.objects.filter(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
|
|
1272
|
+
devicetype = DeviceType.objects.create(
|
|
1273
|
+
manufacturer=self.devices[0].device_type.manufacturer,
|
|
1274
|
+
model="TestDeviceType1",
|
|
1275
|
+
u_height=0,
|
|
1276
|
+
subdevice_role=SubdeviceRoleChoices.ROLE_CHILD,
|
|
1277
|
+
)
|
|
1278
|
+
child_device = Device.objects.create(
|
|
1279
|
+
device_type=devicetype,
|
|
1280
|
+
role=self.devices[0].role,
|
|
1281
|
+
name="TestDevice1",
|
|
1282
|
+
status=self.devices[0].status,
|
|
1283
|
+
location=self.devices[0].location,
|
|
1284
|
+
)
|
|
1285
|
+
DeviceBay.objects.create(device=self.devices[0], name="Device Bay 1", installed_device=child_device)
|
|
1286
|
+
|
|
1287
|
+
def test_assigning_installed_device(self):
|
|
1288
|
+
server = Device.objects.exclude(device_type__subdevice_role=SubdeviceRoleChoices.ROLE_CHILD).last()
|
|
1289
|
+
bay = DeviceBay(device=self.devices[1], name="Device Bay Err", installed_device=server)
|
|
1290
|
+
with self.assertRaises(ValidationError) as err:
|
|
1291
|
+
bay.validated_save()
|
|
1292
|
+
self.assertIn(
|
|
1293
|
+
f'Cannot install device "{server}"; device-type "{server.device_type}" subdevice_role is not "child".',
|
|
1294
|
+
str(err.exception),
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1191
1297
|
|
|
1192
1298
|
class DeviceTypeToSoftwareImageFileTestCase(ModelTestCases.BaseModelTestCase):
|
|
1193
1299
|
model = DeviceTypeToSoftwareImageFile
|
|
@@ -1789,6 +1789,19 @@ class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|
|
1789
1789
|
sorted(interface_ips),
|
|
1790
1790
|
)
|
|
1791
1791
|
|
|
1792
|
+
with self.subTest("Assert Assigning IPAddress Without Selecting Any IPAddress Raises Exception"):
|
|
1793
|
+
assign_ip_form_data["pk"] = []
|
|
1794
|
+
assign_ip_request = {
|
|
1795
|
+
"path": reverse("ipam:ipaddress_assign")
|
|
1796
|
+
+ f"?interface={self.interfaces[1].id}&return_url={device_list_url}",
|
|
1797
|
+
"data": post_data(assign_ip_form_data),
|
|
1798
|
+
}
|
|
1799
|
+
response = self.client.post(**assign_ip_request, follow=True)
|
|
1800
|
+
self.assertHttpStatus(response, 200)
|
|
1801
|
+
self.assertIn(
|
|
1802
|
+
"Please select at least one IP Address from the table.", response.content.decode(response.charset)
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1792
1805
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
|
|
1793
1806
|
def test_device_rearports(self):
|
|
1794
1807
|
device = Device.objects.first()
|
nautobot/dcim/views.py
CHANGED
|
@@ -3029,8 +3029,11 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3029
3029
|
form_class = forms.DeviceRedundancyGroupForm
|
|
3030
3030
|
queryset = (
|
|
3031
3031
|
DeviceRedundancyGroup.objects.select_related("status")
|
|
3032
|
-
.prefetch_related("devices")
|
|
3033
|
-
.annotate(
|
|
3032
|
+
.prefetch_related("controllers", "devices")
|
|
3033
|
+
.annotate(
|
|
3034
|
+
device_count=count_related(Device, "device_redundancy_group"),
|
|
3035
|
+
controller_count=count_related(Controller, "controller_device_redundancy_group"),
|
|
3036
|
+
)
|
|
3034
3037
|
)
|
|
3035
3038
|
serializer_class = serializers.DeviceRedundancyGroupSerializer
|
|
3036
3039
|
table_class = tables.DeviceRedundancyGroupTable
|
|
@@ -3043,6 +3046,9 @@ class DeviceRedundancyGroupUIViewSet(NautobotUIViewSet):
|
|
|
3043
3046
|
devices_table = tables.DeviceTable(devices)
|
|
3044
3047
|
devices_table.columns.show("device_redundancy_group_priority")
|
|
3045
3048
|
context["devices_table"] = devices_table
|
|
3049
|
+
controllers = instance.controllers_sorted.restrict(request.user)
|
|
3050
|
+
controllers_table = tables.ControllerTable(controllers)
|
|
3051
|
+
context["controllers_table"] = controllers_table
|
|
3046
3052
|
return context
|
|
3047
3053
|
|
|
3048
3054
|
|
nautobot/extras/api/views.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
|
-
|
|
3
1
|
from django.conf import settings
|
|
4
2
|
from django.contrib.contenttypes.models import ContentType
|
|
5
3
|
from django.forms import ValidationError as FormsValidationError
|
|
@@ -449,59 +447,6 @@ class ImageAttachmentViewSet(ModelViewSet):
|
|
|
449
447
|
#
|
|
450
448
|
|
|
451
449
|
|
|
452
|
-
def _create_schedule(serializer, data, job_model, user, approval_required, task_queue=None):
|
|
453
|
-
"""
|
|
454
|
-
This is an internal function to create a scheduled job from API data.
|
|
455
|
-
It has to handle both once-offs (i.e. of type TYPE_FUTURE) and interval
|
|
456
|
-
jobs.
|
|
457
|
-
"""
|
|
458
|
-
type_ = serializer["interval"]
|
|
459
|
-
if type_ == JobExecutionType.TYPE_IMMEDIATELY:
|
|
460
|
-
time = timezone.now()
|
|
461
|
-
name = serializer.get("name") or f"{job_model.name} - {time}"
|
|
462
|
-
elif type_ == JobExecutionType.TYPE_CUSTOM:
|
|
463
|
-
time = serializer.get("start_time") # doing .get("key", "default") returns None instead of "default"
|
|
464
|
-
if time is None:
|
|
465
|
-
# "start_time" is checked against models.ScheduledJob.earliest_possible_time()
|
|
466
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
467
|
-
time = timezone.now() + timedelta(seconds=20)
|
|
468
|
-
name = serializer["name"]
|
|
469
|
-
else:
|
|
470
|
-
time = serializer["start_time"]
|
|
471
|
-
name = serializer["name"]
|
|
472
|
-
crontab = serializer.get("crontab", "")
|
|
473
|
-
|
|
474
|
-
celery_kwargs = {
|
|
475
|
-
"nautobot_job_profile": False,
|
|
476
|
-
"queue": task_queue,
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
# 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
|
|
480
|
-
#
|
|
481
|
-
# We pass in task and job_model here partly for forward/backward compatibility logic, and
|
|
482
|
-
# part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
|
|
483
|
-
# FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
|
|
484
|
-
# null) you still have a bit of context on the ScheduledJob as to what it was originally
|
|
485
|
-
# scheduled for.
|
|
486
|
-
scheduled_job = ScheduledJob(
|
|
487
|
-
name=name,
|
|
488
|
-
task=job_model.class_path,
|
|
489
|
-
job_model=job_model,
|
|
490
|
-
start_time=time,
|
|
491
|
-
description=f"Nautobot job {name} scheduled by {user} for {time}",
|
|
492
|
-
kwargs=data,
|
|
493
|
-
celery_kwargs=celery_kwargs,
|
|
494
|
-
interval=type_,
|
|
495
|
-
one_off=(type_ == JobExecutionType.TYPE_FUTURE),
|
|
496
|
-
user=user,
|
|
497
|
-
approval_required=approval_required,
|
|
498
|
-
crontab=crontab,
|
|
499
|
-
queue=task_queue,
|
|
500
|
-
)
|
|
501
|
-
scheduled_job.validated_save()
|
|
502
|
-
return scheduled_job
|
|
503
|
-
|
|
504
|
-
|
|
505
450
|
class JobViewSetBase(
|
|
506
451
|
NautobotAPIVersionMixin,
|
|
507
452
|
# note no CreateModelMixin
|
|
@@ -701,13 +646,16 @@ class JobViewSetBase(
|
|
|
701
646
|
|
|
702
647
|
# Try to create a ScheduledJob, or...
|
|
703
648
|
if schedule_data:
|
|
704
|
-
schedule =
|
|
705
|
-
schedule_data,
|
|
706
|
-
job_class.serialize_data(cleaned_data),
|
|
649
|
+
schedule = ScheduledJob.create_schedule(
|
|
707
650
|
job_model,
|
|
708
651
|
request.user,
|
|
709
|
-
|
|
652
|
+
name=schedule_data.get("name"),
|
|
653
|
+
start_time=schedule_data.get("start_time"),
|
|
654
|
+
interval=schedule_data.get("interval"),
|
|
655
|
+
crontab=schedule_data.get("crontab", ""),
|
|
656
|
+
approval_required=approval_required,
|
|
710
657
|
task_queue=input_serializer.validated_data.get("task_queue", None),
|
|
658
|
+
**job_class.serialize_data(cleaned_data),
|
|
711
659
|
)
|
|
712
660
|
else:
|
|
713
661
|
schedule = None
|
nautobot/extras/homepage.py
CHANGED
|
@@ -7,14 +7,24 @@ def get_job_results(request):
|
|
|
7
7
|
"""Callback function to collect job history for panel."""
|
|
8
8
|
return (
|
|
9
9
|
JobResult.objects.filter(status__in=JobResultStatusChoices.READY_STATES)
|
|
10
|
-
.
|
|
10
|
+
.restrict(request.user, "view")
|
|
11
|
+
.only("id", "name", "status", "date_done", "user")
|
|
11
12
|
.order_by("-date_done")[:10]
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def get_changelog(request):
|
|
16
17
|
"""Callback function to collect changelog for panel."""
|
|
17
|
-
return ObjectChange.objects.restrict(request.user, "view")
|
|
18
|
+
return ObjectChange.objects.restrict(request.user, "view").only(
|
|
19
|
+
"id",
|
|
20
|
+
"action",
|
|
21
|
+
"changed_object",
|
|
22
|
+
"changed_object_id",
|
|
23
|
+
"changed_object_type",
|
|
24
|
+
"object_repr",
|
|
25
|
+
"user_name",
|
|
26
|
+
"time",
|
|
27
|
+
)[:15]
|
|
18
28
|
|
|
19
29
|
|
|
20
30
|
layout = (
|
nautobot/extras/jobs.py
CHANGED
|
@@ -20,7 +20,7 @@ from django.conf import settings
|
|
|
20
20
|
from django.contrib.auth import get_user_model
|
|
21
21
|
from django.core.exceptions import ObjectDoesNotExist
|
|
22
22
|
from django.core.files.base import ContentFile
|
|
23
|
-
from django.core.files.uploadedfile import
|
|
23
|
+
from django.core.files.uploadedfile import UploadedFile
|
|
24
24
|
from django.core.validators import RegexValidator
|
|
25
25
|
from django.db.models import Model
|
|
26
26
|
from django.db.models.query import QuerySet
|
|
@@ -539,7 +539,7 @@ class BaseJob:
|
|
|
539
539
|
elif isinstance(value, Model):
|
|
540
540
|
return_data[field_name] = value.pk
|
|
541
541
|
# FileVar (Save each FileVar as a FileProxy)
|
|
542
|
-
elif isinstance(value,
|
|
542
|
+
elif isinstance(value, UploadedFile):
|
|
543
543
|
return_data[field_name] = BaseJob._save_file_to_proxy(value)
|
|
544
544
|
# IPAddressVar, IPAddressWithMaskVar, IPNetworkVar
|
|
545
545
|
elif isinstance(value, netaddr.ip.BaseIP):
|
nautobot/extras/models/jobs.py
CHANGED
|
@@ -1074,6 +1074,87 @@ class ScheduledJob(BaseModel):
|
|
|
1074
1074
|
day_of_week=day_of_week,
|
|
1075
1075
|
)
|
|
1076
1076
|
|
|
1077
|
+
@classmethod
|
|
1078
|
+
def create_schedule(
|
|
1079
|
+
cls,
|
|
1080
|
+
job_model,
|
|
1081
|
+
user,
|
|
1082
|
+
name=None,
|
|
1083
|
+
start_time=None,
|
|
1084
|
+
interval=JobExecutionType.TYPE_IMMEDIATELY,
|
|
1085
|
+
crontab="",
|
|
1086
|
+
profile=False,
|
|
1087
|
+
approval_required=False,
|
|
1088
|
+
task_queue=None,
|
|
1089
|
+
**job_kwargs,
|
|
1090
|
+
):
|
|
1091
|
+
"""
|
|
1092
|
+
Schedule a job with the specified parameters.
|
|
1093
|
+
|
|
1094
|
+
This method creates a schedule for a job to be executed at a specific time
|
|
1095
|
+
or interval. It handles immediate execution, custom start times, and
|
|
1096
|
+
crontab-based scheduling.
|
|
1097
|
+
|
|
1098
|
+
Parameters:
|
|
1099
|
+
job_model (JobModel): The job model instance.
|
|
1100
|
+
user (User): The user who is scheduling the job.
|
|
1101
|
+
name (str, optional): The name of the scheduled job. Defaults to None.
|
|
1102
|
+
start_time (datetime, optional): The start time for the job. Defaults to None.
|
|
1103
|
+
interval (JobExecutionType, optional): The interval type for the job execution.
|
|
1104
|
+
Defaults to JobExecutionType.TYPE_IMMEDIATELY.
|
|
1105
|
+
crontab (str, optional): The crontab string for the schedule. Defaults to "".
|
|
1106
|
+
profile (bool, optional): Flag indicating whether to profile the job. Defaults to False.
|
|
1107
|
+
approval_required (bool, optional): Flag indicating if approval is required. Defaults to False.
|
|
1108
|
+
task_queue (str, optional): The task queue for the job. Defaults to None, which will use the configured default celery queue.
|
|
1109
|
+
**job_kwargs: Additional keyword arguments to pass to the job.
|
|
1110
|
+
|
|
1111
|
+
Returns:
|
|
1112
|
+
ScheduledJob instance
|
|
1113
|
+
"""
|
|
1114
|
+
|
|
1115
|
+
if interval == JobExecutionType.TYPE_IMMEDIATELY:
|
|
1116
|
+
start_time = timezone.now()
|
|
1117
|
+
name = name or f"{job_model.name} - {start_time}"
|
|
1118
|
+
elif interval == JobExecutionType.TYPE_CUSTOM:
|
|
1119
|
+
if start_time is None:
|
|
1120
|
+
# "start_time" is checked against models.ScheduledJob.earliest_possible_time()
|
|
1121
|
+
# which returns timezone.now() + timedelta(seconds=15)
|
|
1122
|
+
start_time = timezone.now() + timedelta(seconds=20)
|
|
1123
|
+
|
|
1124
|
+
celery_kwargs = {
|
|
1125
|
+
"nautobot_job_profile": profile,
|
|
1126
|
+
"queue": task_queue,
|
|
1127
|
+
}
|
|
1128
|
+
if job_model.soft_time_limit > 0:
|
|
1129
|
+
celery_kwargs["soft_time_limit"] = job_model.soft_time_limit
|
|
1130
|
+
if job_model.time_limit > 0:
|
|
1131
|
+
celery_kwargs["time_limit"] = job_model.time_limit
|
|
1132
|
+
|
|
1133
|
+
# 2.0 TODO: To revisit this as part of a larger Jobs cleanup in 2.0.
|
|
1134
|
+
#
|
|
1135
|
+
# We pass in task and job_model here partly for forward/backward compatibility logic, and
|
|
1136
|
+
# part fallback safety. It's mildly useful to store both the task module/class name and the JobModel
|
|
1137
|
+
# FK on the ScheduledJob, as in the case where the JobModel gets deleted (and the FK becomes
|
|
1138
|
+
# null) you still have a bit of context on the ScheduledJob as to what it was originally
|
|
1139
|
+
# scheduled for.
|
|
1140
|
+
scheduled_job = cls(
|
|
1141
|
+
name=name,
|
|
1142
|
+
task=job_model.class_path,
|
|
1143
|
+
job_model=job_model,
|
|
1144
|
+
start_time=start_time,
|
|
1145
|
+
description=f"Nautobot job {name} scheduled by {user} for {start_time}",
|
|
1146
|
+
kwargs=job_kwargs,
|
|
1147
|
+
celery_kwargs=celery_kwargs,
|
|
1148
|
+
interval=interval,
|
|
1149
|
+
one_off=(interval == JobExecutionType.TYPE_FUTURE),
|
|
1150
|
+
user=user,
|
|
1151
|
+
approval_required=approval_required,
|
|
1152
|
+
crontab=crontab,
|
|
1153
|
+
queue=task_queue,
|
|
1154
|
+
)
|
|
1155
|
+
scheduled_job.validated_save()
|
|
1156
|
+
return scheduled_job
|
|
1157
|
+
|
|
1077
1158
|
def to_cron(self):
|
|
1078
1159
|
t = self.start_time
|
|
1079
1160
|
if self.interval == JobExecutionType.TYPE_HOURLY:
|
|
@@ -244,6 +244,18 @@ class RelationshipModel(models.Model):
|
|
|
244
244
|
if relation.skip_required(cls, opposite_side):
|
|
245
245
|
continue
|
|
246
246
|
|
|
247
|
+
if getattr(relation, f"{relation.required_on}_filter") and instance:
|
|
248
|
+
filterset = get_filterset_for_model(cls)
|
|
249
|
+
if filterset:
|
|
250
|
+
filter_params = getattr(relation, f"{relation.required_on}_filter")
|
|
251
|
+
# If the relationship is required on the model, but the object is not in the filter,
|
|
252
|
+
# we should allow the object to be saved, as the object is not part of the relationship.
|
|
253
|
+
# Example: We want a Device with a Role of Switch to be required to have a relationship
|
|
254
|
+
# with a Device that has a Role of Router. A Device with a Role of Printer should
|
|
255
|
+
# be exempt from the requirement.
|
|
256
|
+
if not filterset(filter_params, cls.objects.filter(id=instance.id)).qs.exists():
|
|
257
|
+
continue
|
|
258
|
+
|
|
247
259
|
if relation.has_many(opposite_side):
|
|
248
260
|
num_required_verbose = "at least one"
|
|
249
261
|
else:
|
nautobot/extras/signals.py
CHANGED
|
@@ -15,7 +15,7 @@ from django.core.cache import cache
|
|
|
15
15
|
from django.core.exceptions import ValidationError
|
|
16
16
|
from django.core.files.storage import get_storage_class
|
|
17
17
|
from django.db import transaction
|
|
18
|
-
from django.db.models.signals import m2m_changed, post_delete, post_save, pre_delete, pre_save
|
|
18
|
+
from django.db.models.signals import m2m_changed, post_delete, post_migrate, post_save, pre_delete, pre_save
|
|
19
19
|
from django.dispatch import receiver
|
|
20
20
|
from django.utils import timezone
|
|
21
21
|
from django_prometheus.models import model_deletes, model_inserts, model_updates
|
|
@@ -268,6 +268,19 @@ def _handle_deleted_object(sender, instance, **kwargs):
|
|
|
268
268
|
model_deletes.labels(instance._meta.model_name).inc()
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
#
|
|
272
|
+
# Content types
|
|
273
|
+
#
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@receiver(post_migrate)
|
|
277
|
+
def post_migrate_clear_content_type_caches(sender, app_config, signal, **kwargs):
|
|
278
|
+
"""Clear various content-type caches after a migration."""
|
|
279
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
280
|
+
cache.delete("nautobot.extras.utils.change_logged_models_queryset")
|
|
281
|
+
cache.delete_pattern("nautobot.extras.utils.FeatureQuery.*")
|
|
282
|
+
|
|
283
|
+
|
|
271
284
|
#
|
|
272
285
|
# Custom fields
|
|
273
286
|
#
|
nautobot/extras/tables.py
CHANGED
|
@@ -99,6 +99,7 @@ GITREPOSITORY_BUTTONS = """
|
|
|
99
99
|
|
|
100
100
|
JOB_BUTTONS = """
|
|
101
101
|
<a href="{% url 'extras:job' pk=record.pk %}" class="btn btn-default btn-xs" title="Details"><i class="mdi mdi-information-outline" aria-hidden="true"></i></a>
|
|
102
|
+
<a href="{% url 'extras:jobresult_list' %}?job_model={{ record.name | urlencode }}" class="btn btn-default btn-xs" title="Job Results"><i class="mdi mdi-format-list-bulleted" aria-hidden="true"></i></a>
|
|
102
103
|
"""
|
|
103
104
|
|
|
104
105
|
OBJECTCHANGE_OBJECT = """
|
|
@@ -639,7 +640,10 @@ class JobTable(BaseTable):
|
|
|
639
640
|
return render_markdown(value)
|
|
640
641
|
|
|
641
642
|
def render_name(self, value):
|
|
642
|
-
return format_html(
|
|
643
|
+
return format_html(
|
|
644
|
+
'<span class="btn btn-primary btn-xs"><i class="mdi mdi-play"></i></span>{}',
|
|
645
|
+
value,
|
|
646
|
+
)
|
|
643
647
|
|
|
644
648
|
class Meta(BaseTable.Meta):
|
|
645
649
|
model = JobModel
|
|
@@ -811,7 +815,16 @@ class JobResultTable(BaseTable):
|
|
|
811
815
|
"summary",
|
|
812
816
|
"actions",
|
|
813
817
|
)
|
|
814
|
-
default_columns = (
|
|
818
|
+
default_columns = (
|
|
819
|
+
"pk",
|
|
820
|
+
"date_created",
|
|
821
|
+
"name",
|
|
822
|
+
"job_model",
|
|
823
|
+
"user",
|
|
824
|
+
"status",
|
|
825
|
+
"summary",
|
|
826
|
+
"actions",
|
|
827
|
+
)
|
|
815
828
|
|
|
816
829
|
|
|
817
830
|
class JobButtonTable(BaseTable):
|
|
@@ -953,7 +966,15 @@ class RelationshipAssociationTable(BaseTable):
|
|
|
953
966
|
|
|
954
967
|
class Meta(BaseTable.Meta):
|
|
955
968
|
model = RelationshipAssociation
|
|
956
|
-
fields = (
|
|
969
|
+
fields = (
|
|
970
|
+
"pk",
|
|
971
|
+
"relationship",
|
|
972
|
+
"source_type",
|
|
973
|
+
"source",
|
|
974
|
+
"destination_type",
|
|
975
|
+
"destination",
|
|
976
|
+
"actions",
|
|
977
|
+
)
|
|
957
978
|
default_columns = ("pk", "relationship", "source", "destination", "actions")
|
|
958
979
|
|
|
959
980
|
|
|
@@ -1069,7 +1090,15 @@ class TagTable(BaseTable):
|
|
|
1069
1090
|
|
|
1070
1091
|
class Meta(BaseTable.Meta):
|
|
1071
1092
|
model = Tag
|
|
1072
|
-
fields = (
|
|
1093
|
+
fields = (
|
|
1094
|
+
"pk",
|
|
1095
|
+
"name",
|
|
1096
|
+
"items",
|
|
1097
|
+
"color",
|
|
1098
|
+
"content_types",
|
|
1099
|
+
"description",
|
|
1100
|
+
"actions",
|
|
1101
|
+
)
|
|
1073
1102
|
|
|
1074
1103
|
|
|
1075
1104
|
class TaggedItemTable(BaseTable):
|
|
@@ -1149,7 +1178,9 @@ class WebhookTable(BaseTable):
|
|
|
1149
1178
|
class AssociatedContactsTable(StatusTableMixin, RoleTableMixin, BaseTable):
|
|
1150
1179
|
pk = ToggleColumn()
|
|
1151
1180
|
contact_type = tables.TemplateColumn(
|
|
1152
|
-
CONTACT_OR_TEAM_ICON,
|
|
1181
|
+
CONTACT_OR_TEAM_ICON,
|
|
1182
|
+
verbose_name="Type",
|
|
1183
|
+
attrs={"td": {"style": "width:20px;"}},
|
|
1153
1184
|
)
|
|
1154
1185
|
name = tables.TemplateColumn(CONTACT_OR_TEAM, verbose_name="Name")
|
|
1155
1186
|
contact_or_team_phone = tables.TemplateColumn(PHONE, accessor="contact_or_team.phone", verbose_name="Phone")
|
|
@@ -115,6 +115,17 @@
|
|
|
115
115
|
<td>{{ object.enabled | render_boolean }}</td>
|
|
116
116
|
<td></td>
|
|
117
117
|
</tr>
|
|
118
|
+
<tr>
|
|
119
|
+
<td>Job Results</td>
|
|
120
|
+
<td>
|
|
121
|
+
{% if object.job_results.exists %}
|
|
122
|
+
<a href="{% url 'extras:jobresult_list' %}?job_model={{ object.name | urlencode }}">{{ object.job_results.count }}</a>
|
|
123
|
+
{% else %}
|
|
124
|
+
{{ None|placeholder }}
|
|
125
|
+
{% endif %}
|
|
126
|
+
</td>
|
|
127
|
+
<td></td>
|
|
128
|
+
</tr>
|
|
118
129
|
</table>
|
|
119
130
|
</div>
|
|
120
131
|
{% endblock content_left_page %}
|
|
@@ -74,7 +74,7 @@ class DynamicGroupTestCase(SeleniumTestCase):
|
|
|
74
74
|
|
|
75
75
|
# And just a cursory check to make sure that the filter worked.
|
|
76
76
|
group = DynamicGroup.objects.get(name=name)
|
|
77
|
-
self.assertEqual(group.count,
|
|
77
|
+
self.assertEqual(group.count, Device.objects.filter(status__name="Active").count())
|
|
78
78
|
self.assertEqual(group.filter, {"status": ["Active"]})
|
|
79
79
|
|
|
80
80
|
# Verify dynamic group shows up on device detail tab
|