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
|
@@ -15,9 +15,11 @@ from nautobot.core.tables import RelationshipColumn
|
|
|
15
15
|
from nautobot.core.testing import TestCase
|
|
16
16
|
from nautobot.core.testing.models import ModelTestCases
|
|
17
17
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
18
|
+
from nautobot.dcim.forms import DeviceForm
|
|
18
19
|
from nautobot.dcim.models import (
|
|
19
20
|
Controller,
|
|
20
21
|
Device,
|
|
22
|
+
DeviceType,
|
|
21
23
|
DeviceTypeToSoftwareImageFile,
|
|
22
24
|
Location,
|
|
23
25
|
LocationType,
|
|
@@ -27,7 +29,7 @@ from nautobot.dcim.models import (
|
|
|
27
29
|
from nautobot.dcim.tables import LocationTable
|
|
28
30
|
from nautobot.dcim.tests.test_views import create_test_device
|
|
29
31
|
from nautobot.extras.choices import RelationshipRequiredSideChoices, RelationshipSideChoices, RelationshipTypeChoices
|
|
30
|
-
from nautobot.extras.models import Relationship, RelationshipAssociation, Status
|
|
32
|
+
from nautobot.extras.models import Relationship, RelationshipAssociation, Role, Status
|
|
31
33
|
from nautobot.ipam.models import VLAN, VLANGroup
|
|
32
34
|
|
|
33
35
|
|
|
@@ -486,6 +488,224 @@ class RelationshipTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
|
486
488
|
with self.assertNumQueries(0):
|
|
487
489
|
manager_method(Location)
|
|
488
490
|
|
|
491
|
+
def test_required_related_object_errors(self):
|
|
492
|
+
"""
|
|
493
|
+
Confirm that the fix in https://github.com/nautobot/nautobot/pull/5570 is working as expected
|
|
494
|
+
"""
|
|
495
|
+
device_ct = ContentType.objects.get_for_model(Device)
|
|
496
|
+
status = Status.objects.get_for_model(Device).first()
|
|
497
|
+
device_type = DeviceType.objects.exclude(manufacturer__isnull=True).first()
|
|
498
|
+
# Create a Device with role Role 1
|
|
499
|
+
role_1 = Role.objects.create(name="Role 1")
|
|
500
|
+
role_1.content_types.add(ContentType.objects.get_for_model(Device))
|
|
501
|
+
device_1 = Device.objects.create(
|
|
502
|
+
device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
|
|
503
|
+
)
|
|
504
|
+
# Create a Device with role Role 2
|
|
505
|
+
role_2 = Role.objects.create(name="Role 2")
|
|
506
|
+
role_2.content_types.add(ContentType.objects.get_for_model(Device))
|
|
507
|
+
device_2 = Device.objects.create(
|
|
508
|
+
device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
|
|
509
|
+
)
|
|
510
|
+
# Create a Device with role Role 3
|
|
511
|
+
role_3 = Role.objects.create(name="Role 3")
|
|
512
|
+
role_3.content_types.add(ContentType.objects.get_for_model(Device))
|
|
513
|
+
device_3 = Device.objects.create(
|
|
514
|
+
device_type=device_type, role=role_3, name="Device 3", location=self.locations[0], status=status
|
|
515
|
+
)
|
|
516
|
+
# Create a one-to-many relationship with destination required, source filter: {"role": ["Role 1"]}
|
|
517
|
+
# and destination filter {"role": ["Role 2"]}
|
|
518
|
+
relationship = Relationship.objects.create(
|
|
519
|
+
label="Device to Devices",
|
|
520
|
+
key="device_to_devices",
|
|
521
|
+
source_type=device_ct,
|
|
522
|
+
source_filter={"role": ["Role 1"]},
|
|
523
|
+
destination_type=device_ct,
|
|
524
|
+
destination_filter={"role": ["Role 2"]},
|
|
525
|
+
type=RelationshipTypeChoices.TYPE_ONE_TO_MANY,
|
|
526
|
+
required_on="destination",
|
|
527
|
+
)
|
|
528
|
+
# Attempt to update device_3 which will not be in the queryset filtered by the destination filter
|
|
529
|
+
# Assert that the form is valid and no ValueError is raised.
|
|
530
|
+
update_status = Status.objects.get_for_model(Device).last()
|
|
531
|
+
update_data_for_device_3 = {
|
|
532
|
+
"location": device_3.location.pk,
|
|
533
|
+
"device_type": device_3.device_type.pk,
|
|
534
|
+
"role": device_3.role.pk,
|
|
535
|
+
"name": device_3.name,
|
|
536
|
+
"status": update_status.pk,
|
|
537
|
+
}
|
|
538
|
+
form = DeviceForm(instance=device_3, data=update_data_for_device_3)
|
|
539
|
+
self.assertTrue(form.is_valid())
|
|
540
|
+
# Attempt to update device_1 which will not be in the destination filter,
|
|
541
|
+
# but is in the source filter.
|
|
542
|
+
update_data_for_device_1 = {
|
|
543
|
+
"location": device_1.location.pk,
|
|
544
|
+
"device_type": device_1.device_type.pk,
|
|
545
|
+
"role": device_1.role.pk,
|
|
546
|
+
"name": device_1.name,
|
|
547
|
+
"status": update_status.pk,
|
|
548
|
+
}
|
|
549
|
+
form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
550
|
+
self.assertTrue(form2.is_valid())
|
|
551
|
+
# Attempt to update device_2 which will be in the destination filter, so it should
|
|
552
|
+
# require the relationship.
|
|
553
|
+
update_data_for_device_2 = {
|
|
554
|
+
"location": device_2.location.pk,
|
|
555
|
+
"device_type": device_2.device_type.pk,
|
|
556
|
+
"role": device_2.role.pk,
|
|
557
|
+
"name": "Device 2",
|
|
558
|
+
"status": update_status.pk,
|
|
559
|
+
}
|
|
560
|
+
form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
561
|
+
self.assertFalse(form3.is_valid())
|
|
562
|
+
# Device 1 has a relationship to Device 2
|
|
563
|
+
update_data_for_device_1 = {
|
|
564
|
+
"location": device_1.location.pk,
|
|
565
|
+
"device_type": device_1.device_type.pk,
|
|
566
|
+
"role": device_1.role.pk,
|
|
567
|
+
"name": device_1.name,
|
|
568
|
+
"status": update_status.pk,
|
|
569
|
+
"cr_device_to_devices__destination": [device_2.pk],
|
|
570
|
+
}
|
|
571
|
+
form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
572
|
+
self.assertTrue(form4.is_valid())
|
|
573
|
+
form4.save()
|
|
574
|
+
# Device 2 has a relationship to Device 1, form should validate and save.
|
|
575
|
+
update_data_for_device_2 = {
|
|
576
|
+
"location": device_2.location.pk,
|
|
577
|
+
"device_type": device_2.device_type.pk,
|
|
578
|
+
"role": device_2.role.pk,
|
|
579
|
+
"name": "Device 2",
|
|
580
|
+
"status": update_status.pk,
|
|
581
|
+
"cr_device_to_devices__source": device_1.pk,
|
|
582
|
+
}
|
|
583
|
+
form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
584
|
+
self.assertTrue(form5.is_valid())
|
|
585
|
+
form5.save()
|
|
586
|
+
# Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
587
|
+
update_data_for_device_2 = {
|
|
588
|
+
"location": device_2.location.pk,
|
|
589
|
+
"device_type": device_2.device_type.pk,
|
|
590
|
+
"role": device_2.role.pk,
|
|
591
|
+
"name": "Device 2",
|
|
592
|
+
"status": update_status.pk,
|
|
593
|
+
"cr_device_to_devices__source": device_3.pk,
|
|
594
|
+
}
|
|
595
|
+
form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
596
|
+
with self.assertRaises(ValidationError):
|
|
597
|
+
form6.save()
|
|
598
|
+
# Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
599
|
+
update_data_for_device_1 = {
|
|
600
|
+
"location": device_1.location.pk,
|
|
601
|
+
"device_type": device_1.device_type.pk,
|
|
602
|
+
"role": device_1.role.pk,
|
|
603
|
+
"name": "Device 1",
|
|
604
|
+
"status": update_status.pk,
|
|
605
|
+
"cr_device_to_devices__destination": [
|
|
606
|
+
device_3.pk,
|
|
607
|
+
],
|
|
608
|
+
}
|
|
609
|
+
form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
610
|
+
with self.assertRaises(ValidationError):
|
|
611
|
+
form6.save()
|
|
612
|
+
|
|
613
|
+
relationship.required_on = "source"
|
|
614
|
+
relationship.save()
|
|
615
|
+
# Attempt to update device_3 which will not be in the queryset filtered by the destination filter
|
|
616
|
+
# Assert that the form is valid and no ValueError is raised. This ensures that an object that
|
|
617
|
+
# does not take part in any relationships can still be updated, addressing issue #5569.
|
|
618
|
+
update_status = Status.objects.get_for_model(Device).last()
|
|
619
|
+
update_data_for_device_3 = {
|
|
620
|
+
"location": device_3.location.pk,
|
|
621
|
+
"device_type": device_3.device_type.pk,
|
|
622
|
+
"role": device_3.role.pk,
|
|
623
|
+
"name": device_3.name,
|
|
624
|
+
"status": update_status.pk,
|
|
625
|
+
}
|
|
626
|
+
form = DeviceForm(instance=device_3, data=update_data_for_device_3)
|
|
627
|
+
self.assertTrue(form.is_valid())
|
|
628
|
+
# Attempt to update device_1 which will not be in the destination filter,
|
|
629
|
+
# but is in the source filter. Should fail as device_to_devices is required.
|
|
630
|
+
device_1.delete()
|
|
631
|
+
device_1 = Device.objects.create(
|
|
632
|
+
device_type=device_type, role=role_1, name="Device 1", location=self.locations[0], status=status
|
|
633
|
+
)
|
|
634
|
+
update_data_for_device_1 = {
|
|
635
|
+
"location": device_1.location.pk,
|
|
636
|
+
"device_type": device_1.device_type.pk,
|
|
637
|
+
"role": device_1.role.pk,
|
|
638
|
+
"name": device_1.name,
|
|
639
|
+
"status": update_status.pk,
|
|
640
|
+
}
|
|
641
|
+
form2 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
642
|
+
self.assertFalse(form2.is_valid())
|
|
643
|
+
# Attempt to update device_2 which will be in the destination filter, which should not require the
|
|
644
|
+
# relationship anymore.
|
|
645
|
+
device_2.delete()
|
|
646
|
+
device_2 = Device.objects.create(
|
|
647
|
+
device_type=device_type, role=role_2, name="Device 2", location=self.locations[0], status=status
|
|
648
|
+
)
|
|
649
|
+
update_data_for_device_2 = {
|
|
650
|
+
"location": device_2.location.pk,
|
|
651
|
+
"device_type": device_2.device_type.pk,
|
|
652
|
+
"role": device_2.role.pk,
|
|
653
|
+
"name": "Device 2",
|
|
654
|
+
"status": update_status.pk,
|
|
655
|
+
}
|
|
656
|
+
form3 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
657
|
+
self.assertTrue(form3.is_valid())
|
|
658
|
+
# Device 1 has a relationship to Device 2
|
|
659
|
+
update_data_for_device_1 = {
|
|
660
|
+
"location": device_1.location.pk,
|
|
661
|
+
"device_type": device_1.device_type.pk,
|
|
662
|
+
"role": device_1.role.pk,
|
|
663
|
+
"name": device_1.name,
|
|
664
|
+
"status": update_status.pk,
|
|
665
|
+
"cr_device_to_devices__destination": [device_2.pk],
|
|
666
|
+
}
|
|
667
|
+
form4 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
668
|
+
self.assertTrue(form4.is_valid())
|
|
669
|
+
form4.save()
|
|
670
|
+
# Device 2 has a relationship to Device 1, form should validate and save.
|
|
671
|
+
update_data_for_device_2 = {
|
|
672
|
+
"location": device_2.location.pk,
|
|
673
|
+
"device_type": device_2.device_type.pk,
|
|
674
|
+
"role": device_2.role.pk,
|
|
675
|
+
"name": "Device 2",
|
|
676
|
+
"status": update_status.pk,
|
|
677
|
+
"cr_device_to_devices__source": device_1.pk,
|
|
678
|
+
}
|
|
679
|
+
form5 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
680
|
+
self.assertTrue(form5.is_valid())
|
|
681
|
+
form5.save()
|
|
682
|
+
# Device 2 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
683
|
+
update_data_for_device_2 = {
|
|
684
|
+
"location": device_2.location.pk,
|
|
685
|
+
"device_type": device_2.device_type.pk,
|
|
686
|
+
"role": device_2.role.pk,
|
|
687
|
+
"name": "Device 2",
|
|
688
|
+
"status": update_status.pk,
|
|
689
|
+
"cr_device_to_devices__source": device_3.pk,
|
|
690
|
+
}
|
|
691
|
+
form6 = DeviceForm(instance=device_2, data=update_data_for_device_2)
|
|
692
|
+
with self.assertRaises(ValidationError):
|
|
693
|
+
form6.save()
|
|
694
|
+
# Device 1 has a relationship to Device 3, save should fail as Device 3 doesn't match filter.
|
|
695
|
+
update_data_for_device_1 = {
|
|
696
|
+
"location": device_1.location.pk,
|
|
697
|
+
"device_type": device_1.device_type.pk,
|
|
698
|
+
"role": device_1.role.pk,
|
|
699
|
+
"name": "Device 1",
|
|
700
|
+
"status": update_status.pk,
|
|
701
|
+
"cr_device_to_devices__destination": [
|
|
702
|
+
device_3.pk,
|
|
703
|
+
],
|
|
704
|
+
}
|
|
705
|
+
form6 = DeviceForm(instance=device_1, data=update_data_for_device_1)
|
|
706
|
+
with self.assertRaises(ValidationError):
|
|
707
|
+
form6.save()
|
|
708
|
+
|
|
489
709
|
|
|
490
710
|
class RelationshipAssociationTest(RelationshipBaseTest, ModelTestCases.BaseModelTestCase):
|
|
491
711
|
model = RelationshipAssociation
|
|
@@ -2532,6 +2532,27 @@ class JobButtonRenderingTestCase(TestCase):
|
|
|
2532
2532
|
)
|
|
2533
2533
|
|
|
2534
2534
|
|
|
2535
|
+
class JobCustomTemplateTestCase(TestCase):
|
|
2536
|
+
@classmethod
|
|
2537
|
+
def setUpTestData(cls):
|
|
2538
|
+
# Job model objects are automatically created during database migrations
|
|
2539
|
+
|
|
2540
|
+
# But we do need to make sure the ones we're testing are flagged appropriately
|
|
2541
|
+
cls.example_job = Job.objects.get(job_class_name="ExampleCustomFormJob")
|
|
2542
|
+
cls.example_job.enabled = True
|
|
2543
|
+
cls.example_job.save()
|
|
2544
|
+
|
|
2545
|
+
cls.run_url = reverse("extras:job_run", kwargs={"pk": cls.example_job.pk})
|
|
2546
|
+
|
|
2547
|
+
def test_rendering_custom_template(self):
|
|
2548
|
+
obj_perm = ObjectPermission(name="Test permission", actions=["view", "run"])
|
|
2549
|
+
obj_perm.save()
|
|
2550
|
+
obj_perm.users.add(self.user)
|
|
2551
|
+
obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
|
|
2552
|
+
with self.assertTemplateUsed("example_app/custom_job_form.html"):
|
|
2553
|
+
self.client.get(self.run_url)
|
|
2554
|
+
|
|
2555
|
+
|
|
2535
2556
|
# TODO: Convert to StandardTestCases.Views
|
|
2536
2557
|
class ObjectChangeTestCase(TestCase):
|
|
2537
2558
|
user_permissions = ("extras.view_objectchange",)
|
nautobot/extras/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections
|
|
2
|
+
import contextlib
|
|
2
3
|
import hashlib
|
|
3
4
|
import hmac
|
|
4
5
|
import logging
|
|
@@ -14,6 +15,7 @@ from django.db import transaction
|
|
|
14
15
|
from django.db.models import Q
|
|
15
16
|
from django.template.loader import get_template, TemplateDoesNotExist
|
|
16
17
|
from django.utils.deconstruct import deconstructible
|
|
18
|
+
import redis.exceptions
|
|
17
19
|
|
|
18
20
|
from nautobot.core.choices import ColorChoices
|
|
19
21
|
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
@@ -109,12 +111,17 @@ class ChangeLoggedModelsQuery(FeaturedQueryMixin):
|
|
|
109
111
|
def change_logged_models_queryset():
|
|
110
112
|
"""
|
|
111
113
|
Cacheable function for cases where we need this queryset many times, such as when saving multiple objects.
|
|
114
|
+
|
|
115
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
112
116
|
"""
|
|
117
|
+
queryset = None
|
|
113
118
|
cache_key = "nautobot.extras.utils.change_logged_models_queryset"
|
|
114
|
-
|
|
119
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
120
|
+
queryset = cache.get(cache_key)
|
|
115
121
|
if queryset is None:
|
|
116
122
|
queryset = ChangeLoggedModelsQuery().as_queryset()
|
|
117
|
-
|
|
123
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
124
|
+
cache.set(cache_key, queryset)
|
|
118
125
|
return queryset
|
|
119
126
|
|
|
120
127
|
|
|
@@ -163,12 +170,34 @@ class FeatureQuery:
|
|
|
163
170
|
|
|
164
171
|
>>> FeatureQuery('statuses').get_choices()
|
|
165
172
|
[('dcim.device', 13), ('dcim.rack', 34)]
|
|
173
|
+
|
|
174
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
166
175
|
"""
|
|
167
|
-
|
|
176
|
+
choices = None
|
|
177
|
+
cache_key = f"nautobot.extras.utils.FeatureQuery.choices.{self.feature}"
|
|
178
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
179
|
+
choices = cache.get(cache_key)
|
|
180
|
+
if choices is None:
|
|
181
|
+
choices = [(f"{ct.app_label}.{ct.model}", ct.pk) for ct in ContentType.objects.filter(self.get_query())]
|
|
182
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
183
|
+
cache.set(cache_key, choices)
|
|
184
|
+
return choices
|
|
168
185
|
|
|
169
186
|
def list_subclasses(self):
|
|
170
|
-
"""
|
|
171
|
-
|
|
187
|
+
"""
|
|
188
|
+
Return a list of model classes that declare this feature.
|
|
189
|
+
|
|
190
|
+
Cache is cleared by post_migrate signal (nautobot.extras.signals.post_migrate_clear_content_type_caches).
|
|
191
|
+
"""
|
|
192
|
+
subclasses = None
|
|
193
|
+
cache_key = f"nautobot.extras.utils.FeatureQuery.subclasses.{self.feature}"
|
|
194
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
195
|
+
subclasses = cache.get(cache_key)
|
|
196
|
+
if subclasses is None:
|
|
197
|
+
subclasses = [ct.model_class() for ct in ContentType.objects.filter(self.get_query())]
|
|
198
|
+
with contextlib.suppress(redis.exceptions.ConnectionError):
|
|
199
|
+
cache.set(cache_key, subclasses)
|
|
200
|
+
return subclasses
|
|
172
201
|
|
|
173
202
|
|
|
174
203
|
@deconstructible
|
nautobot/extras/views.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from datetime import timedelta
|
|
2
1
|
import logging
|
|
3
2
|
|
|
4
3
|
from celery import chain
|
|
@@ -1345,55 +1344,25 @@ class JobRunView(ObjectPermissionRequiredMixin, View):
|
|
|
1345
1344
|
schedule_type = schedule_form.cleaned_data["_schedule_type"]
|
|
1346
1345
|
|
|
1347
1346
|
if (not dryrun and job_model.approval_required) or schedule_type in JobExecutionType.SCHEDULE_CHOICES:
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
# as a once in the future task with the due date set to the current time. This means
|
|
1354
|
-
# when approval is granted, the task is immediately due for execution.
|
|
1355
|
-
schedule_type = JobExecutionType.TYPE_FUTURE
|
|
1356
|
-
schedule_datetime = timezone.now()
|
|
1357
|
-
schedule_name = f"{job_model} - {schedule_datetime}"
|
|
1358
|
-
|
|
1359
|
-
else:
|
|
1360
|
-
schedule_name = schedule_form.cleaned_data["_schedule_name"]
|
|
1361
|
-
|
|
1362
|
-
if schedule_type == JobExecutionType.TYPE_CUSTOM:
|
|
1363
|
-
crontab = schedule_form.cleaned_data["_recurrence_custom_time"]
|
|
1364
|
-
# doing .get("key", "default") returns None instead of "default" here for some reason
|
|
1365
|
-
schedule_datetime = schedule_form.cleaned_data.get("_schedule_start_time")
|
|
1366
|
-
if schedule_datetime is None:
|
|
1367
|
-
# "_schedule_start_time" is checked against ScheduledJob.earliest_possible_time()
|
|
1368
|
-
# which returns timezone.now() + timedelta(seconds=15)
|
|
1369
|
-
schedule_datetime = timezone.now() + timedelta(seconds=20)
|
|
1370
|
-
else:
|
|
1371
|
-
schedule_datetime = schedule_form.cleaned_data["_schedule_start_time"]
|
|
1372
|
-
|
|
1373
|
-
celery_kwargs = {"nautobot_job_profile": profile, "queue": task_queue}
|
|
1374
|
-
scheduled_job = ScheduledJob(
|
|
1375
|
-
name=schedule_name,
|
|
1376
|
-
task=job_model.class_path,
|
|
1377
|
-
job_model=job_model,
|
|
1378
|
-
start_time=schedule_datetime,
|
|
1379
|
-
description=f"Nautobot job {schedule_name} scheduled by {request.user} for {schedule_datetime}",
|
|
1380
|
-
kwargs=job_class.serialize_data(job_form.cleaned_data),
|
|
1381
|
-
celery_kwargs=celery_kwargs,
|
|
1347
|
+
scheduled_job = ScheduledJob.create_schedule(
|
|
1348
|
+
job_model,
|
|
1349
|
+
request.user,
|
|
1350
|
+
name=schedule_form.cleaned_data.get("_schedule_name"),
|
|
1351
|
+
start_time=schedule_form.cleaned_data.get("_schedule_start_time"),
|
|
1382
1352
|
interval=schedule_type,
|
|
1383
|
-
|
|
1384
|
-
queue=task_queue,
|
|
1385
|
-
user=request.user,
|
|
1353
|
+
crontab=schedule_form.cleaned_data.get("_recurrence_custom_time"),
|
|
1386
1354
|
approval_required=job_model.approval_required,
|
|
1387
|
-
|
|
1355
|
+
task_queue=task_queue,
|
|
1356
|
+
profile=profile,
|
|
1357
|
+
**job_class.serialize_data(job_form.cleaned_data),
|
|
1388
1358
|
)
|
|
1389
|
-
scheduled_job.validated_save()
|
|
1390
1359
|
|
|
1391
1360
|
if job_model.approval_required:
|
|
1392
|
-
messages.success(request, f"Job {
|
|
1393
|
-
return redirect(return_url
|
|
1361
|
+
messages.success(request, f"Job {scheduled_job.name} successfully submitted for approval")
|
|
1362
|
+
return redirect(return_url or "extras:scheduledjob_approval_queue_list")
|
|
1394
1363
|
else:
|
|
1395
|
-
messages.success(request, f"Job {
|
|
1396
|
-
return redirect(return_url
|
|
1364
|
+
messages.success(request, f"Job {scheduled_job.name} successfully scheduled")
|
|
1365
|
+
return redirect(return_url or "extras:scheduledjob_list")
|
|
1397
1366
|
|
|
1398
1367
|
else:
|
|
1399
1368
|
# Enqueue job for immediate execution
|
|
@@ -1787,8 +1756,13 @@ class JobLogEntryTableView(generic.GenericView):
|
|
|
1787
1756
|
else:
|
|
1788
1757
|
queryset = instance.job_log_entries.all()
|
|
1789
1758
|
log_table = tables.JobLogEntryTable(data=queryset, user=request.user)
|
|
1790
|
-
|
|
1791
|
-
|
|
1759
|
+
paginate = {
|
|
1760
|
+
"paginator_class": EnhancedPaginator,
|
|
1761
|
+
"per_page": get_paginate_count(request),
|
|
1762
|
+
}
|
|
1763
|
+
RequestConfig(request, paginate).configure(log_table)
|
|
1764
|
+
table = log_table.as_html(request)
|
|
1765
|
+
return HttpResponse(table)
|
|
1792
1766
|
|
|
1793
1767
|
|
|
1794
1768
|
#
|
nautobot/ipam/models.py
CHANGED
|
@@ -1011,7 +1011,7 @@ class IPAddress(PrimaryModel):
|
|
|
1011
1011
|
parent = models.ForeignKey(
|
|
1012
1012
|
"ipam.Prefix",
|
|
1013
1013
|
blank=True,
|
|
1014
|
-
null=True,
|
|
1014
|
+
null=True, # TODO remove this, it shouldn't be permitted for the database!
|
|
1015
1015
|
related_name="ip_addresses", # `IPAddress` to use `related_name="ip_addresses"`
|
|
1016
1016
|
on_delete=models.PROTECT,
|
|
1017
1017
|
help_text="The parent Prefix of this IPAddress.",
|
|
@@ -1108,7 +1108,7 @@ class IPAddress(PrimaryModel):
|
|
|
1108
1108
|
raise ValidationError({"namespace": "No suitable parent Prefix exists in this Namespace"}) from e
|
|
1109
1109
|
|
|
1110
1110
|
def clean(self):
|
|
1111
|
-
|
|
1111
|
+
self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
|
|
1112
1112
|
|
|
1113
1113
|
# Validate that host is not being modified
|
|
1114
1114
|
if self.present_in_database:
|
|
@@ -1122,8 +1122,8 @@ class IPAddress(PrimaryModel):
|
|
|
1122
1122
|
|
|
1123
1123
|
closest_parent = self._get_closest_parent()
|
|
1124
1124
|
# Validate `parent` can be used as the parent for this ipaddress
|
|
1125
|
-
if
|
|
1126
|
-
if self.parent != closest_parent:
|
|
1125
|
+
if closest_parent is not None:
|
|
1126
|
+
if self.parent is not None and self.parent != closest_parent:
|
|
1127
1127
|
raise ValidationError(
|
|
1128
1128
|
{
|
|
1129
1129
|
"parent": (
|
|
@@ -1135,23 +1135,20 @@ class IPAddress(PrimaryModel):
|
|
|
1135
1135
|
self.parent = closest_parent
|
|
1136
1136
|
self._namespace = None
|
|
1137
1137
|
|
|
1138
|
-
def save(self, *args, **kwargs):
|
|
1139
1138
|
# 3.0 TODO: uncomment the below to enforce this constraint
|
|
1140
1139
|
# if self.parent.type != choices.PrefixTypeChoices.TYPE_NETWORK:
|
|
1141
1140
|
# err_msg = f"IP addresses cannot be created in {self.parent.type} prefixes. You must create a network prefix first."
|
|
1142
1141
|
# raise ValidationError({"address": err_msg})
|
|
1143
1142
|
|
|
1144
|
-
self.address = self.address # not a no-op - forces re-calling of self._deconstruct_address()
|
|
1145
|
-
|
|
1146
1143
|
# Force dns_name to lowercase
|
|
1147
1144
|
if not self.dns_name.islower:
|
|
1148
1145
|
self.dns_name = self.dns_name.lower()
|
|
1149
1146
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1147
|
+
super().clean()
|
|
1148
|
+
|
|
1149
|
+
def save(self, *args, **kwargs):
|
|
1150
|
+
self.clean() # MUST do data fixup as above
|
|
1151
|
+
|
|
1155
1152
|
super().save(*args, **kwargs)
|
|
1156
1153
|
|
|
1157
1154
|
@property
|
|
@@ -997,8 +997,9 @@ class TestIPAddress(ModelTestCases.BaseModelTestCase):
|
|
|
997
997
|
def test_duplicate_global_unique(self):
|
|
998
998
|
"""Test that duplicate IPs in the same Namespace raises an error."""
|
|
999
999
|
IPAddress.objects.create(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
|
|
1000
|
-
|
|
1001
|
-
|
|
1000
|
+
duplicate_ip = IPAddress(address="192.0.2.1/24", status=self.status, namespace=self.namespace)
|
|
1001
|
+
with self.assertRaises(ValidationError):
|
|
1002
|
+
duplicate_ip.full_clean()
|
|
1002
1003
|
|
|
1003
1004
|
def test_multiple_nat_outside_list(self):
|
|
1004
1005
|
"""
|
nautobot/ipam/views.py
CHANGED
|
@@ -943,14 +943,8 @@ class IPAddressAssignView(view_mixins.GetReturnURLMixin, generic.ObjectView):
|
|
|
943
943
|
ip_addresses = IPAddress.objects.restrict(request.user, "view").filter(pk__in=pks)
|
|
944
944
|
interface.ip_addresses.add(*ip_addresses)
|
|
945
945
|
return redirect(self.get_return_url(request))
|
|
946
|
-
|
|
947
|
-
return
|
|
948
|
-
request,
|
|
949
|
-
"ipam/ipaddress_assign.html",
|
|
950
|
-
{
|
|
951
|
-
"return_url": self.get_return_url(request),
|
|
952
|
-
},
|
|
953
|
-
)
|
|
946
|
+
messages.error(request, "Please select at least one IP Address from the table.")
|
|
947
|
+
return redirect(request.get_full_path())
|
|
954
948
|
|
|
955
949
|
|
|
956
950
|
class IPAddressMergeView(view_mixins.GetReturnURLMixin, view_mixins.ObjectPermissionRequiredMixin, View):
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
<link rel="icon" href="/projects/core/en/stable/assets/favicon.ico">
|
|
15
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
15
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.
|
|
23
|
+
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/main.76a95c52.min.css">
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
<link rel="stylesheet" href="/projects/core/en/stable/assets/stylesheets/palette.06af60db.min.css">
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
|
|
219
219
|
|
|
220
220
|
<li class="md-tabs__item">
|
|
221
|
-
<a href="/projects/core/en/stable/
|
|
221
|
+
<a href="/projects/core/en/stable/index.html" class="md-tabs__link">
|
|
222
222
|
|
|
223
223
|
|
|
224
224
|
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
|
|
388
388
|
|
|
389
389
|
<div class="md-nav__link md-nav__container">
|
|
390
|
-
<a href="/projects/core/en/stable/
|
|
390
|
+
<a href="/projects/core/en/stable/index.html" class="md-nav__link ">
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
<span class="md-ellipsis">
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
<link rel="icon" href="../assets/favicon.ico">
|
|
21
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
21
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
<link rel="stylesheet" href="../assets/stylesheets/main.
|
|
29
|
+
<link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
|
|
239
239
|
|
|
240
240
|
<li class="md-tabs__item">
|
|
241
|
-
<a href="../
|
|
241
|
+
<a href="../index.html" class="md-tabs__link">
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
|
|
@@ -409,7 +409,7 @@
|
|
|
409
409
|
|
|
410
410
|
|
|
411
411
|
<div class="md-nav__link md-nav__container">
|
|
412
|
-
<a href="../
|
|
412
|
+
<a href="../index.html" class="md-nav__link ">
|
|
413
413
|
|
|
414
414
|
|
|
415
415
|
<span class="md-ellipsis">
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
<link rel="icon" href="../assets/favicon.ico">
|
|
19
|
-
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.
|
|
19
|
+
<meta name="generator" content="mkdocs-1.6.0, mkdocs-material-9.5.29">
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
<link rel="stylesheet" href="../assets/stylesheets/main.
|
|
27
|
+
<link rel="stylesheet" href="../assets/stylesheets/main.76a95c52.min.css">
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
|
|
237
237
|
|
|
238
238
|
<li class="md-tabs__item">
|
|
239
|
-
<a href="../
|
|
239
|
+
<a href="../index.html" class="md-tabs__link">
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
|
|
@@ -407,7 +407,7 @@
|
|
|
407
407
|
|
|
408
408
|
|
|
409
409
|
<div class="md-nav__link md-nav__container">
|
|
410
|
-
<a href="../
|
|
410
|
+
<a href="../index.html" class="md-nav__link ">
|
|
411
411
|
|
|
412
412
|
|
|
413
413
|
<span class="md-ellipsis">
|