nautobot 2.2.8__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 +2 -2
- nautobot/core/graphql/schema.py +2 -3
- 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 +1 -1
- 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 +4 -0
- nautobot/dcim/tables/devices.py +19 -4
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
- nautobot/dcim/tests/test_models.py +31 -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/signals.py +14 -1
- nautobot/extras/tables.py +36 -5
- nautobot/extras/templates/extras/job_detail.html +11 -0
- 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 +2 -2
- nautobot/project-static/docs/apps/index.html +2 -2
- nautobot/project-static/docs/apps/nautobot-apps.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +2 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +4 -2
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2 -2
- nautobot/project-static/docs/development/apps/api/configuration-view.html +2 -2
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/global-search.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/graphql.html +2 -2
- nautobot/project-static/docs/development/apps/api/models/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +2 -2
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +2 -2
- nautobot/project-static/docs/development/apps/api/prometheus.html +2 -2
- nautobot/project-static/docs/development/apps/api/setup.html +2 -2
- nautobot/project-static/docs/development/apps/api/testing.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +2 -2
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/base-template.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/index.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/notes.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +2 -2
- nautobot/project-static/docs/development/apps/api/views/urls.html +2 -2
- nautobot/project-static/docs/development/apps/index.html +2 -2
- nautobot/project-static/docs/development/apps/migration/code-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +2 -2
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +2 -2
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +2 -2
- nautobot/project-static/docs/development/core/application-registry.html +2 -2
- nautobot/project-static/docs/development/core/best-practices.html +2 -2
- nautobot/project-static/docs/development/core/bootstrap-ui.html +2 -2
- nautobot/project-static/docs/development/core/caching.html +2 -2
- nautobot/project-static/docs/development/core/controllers.html +2 -2
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +2 -2
- nautobot/project-static/docs/development/core/generic-views.html +2 -2
- nautobot/project-static/docs/development/core/getting-started.html +2 -2
- nautobot/project-static/docs/development/core/homepage.html +2 -2
- nautobot/project-static/docs/development/core/index.html +13 -2
- nautobot/project-static/docs/development/core/model-checklist.html +2 -2
- nautobot/project-static/docs/development/core/model-features.html +2 -2
- nautobot/project-static/docs/development/core/natural-keys.html +2 -2
- nautobot/project-static/docs/development/core/navigation-menu.html +2 -2
- nautobot/project-static/docs/development/core/release-checklist.html +2 -2
- nautobot/project-static/docs/development/core/role-internals.html +2 -2
- nautobot/project-static/docs/development/core/settings.html +2 -2
- nautobot/project-static/docs/development/core/style-guide.html +2 -2
- nautobot/project-static/docs/development/core/templates.html +2 -2
- nautobot/project-static/docs/development/core/testing.html +2 -2
- nautobot/project-static/docs/development/core/user-preferences.html +2 -2
- nautobot/project-static/docs/development/index.html +2 -2
- nautobot/project-static/docs/development/jobs/index.html +377 -363
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +2 -2
- nautobot/project-static/docs/index.html +8228 -13
- nautobot/project-static/docs/overview/application_stack.html +2 -2
- nautobot/project-static/docs/overview/design_philosophy.html +4 -4
- nautobot/project-static/docs/overview/index.html +13 -8228
- nautobot/project-static/docs/release-notes/index.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.3.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.4.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.5.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.6.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-2.2.html +232 -95
- 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 +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +34 -2
- nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +2 -2
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/caching.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +6 -2
- nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +2 -2
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/services.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +2 -2
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +2 -2
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +60 -8
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +2 -2
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +2 -2
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +2 -2
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +2 -2
- nautobot/project-static/docs/user-guide/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +2 -2
- nautobot/virtualization/tables.py +2 -5
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/METADATA +2 -2
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/RECORD +299 -299
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/NOTICE +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/WHEEL +0 -0
- {nautobot-2.2.8.dist-info → nautobot-2.2.9.dist-info}/entry_points.txt +0 -0
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:
|
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 %}
|
|
@@ -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):
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|
|
@@ -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">
|