nautobot 3.0.0a2__py3-none-any.whl → 3.0.0a3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/choices.py +0 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +9 -1
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit.html +1 -1
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination.html +1 -1
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/circuittype.html +1 -1
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/templates/circuits/providernetwork.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/cloud/filters.py +1 -1
- nautobot/cloud/navigation.py +3 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +6 -1
- nautobot/core/api/urls.py +1 -0
- nautobot/core/api/views.py +8 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +70 -1
- nautobot/core/cli/migrate_deprecated_templates.py +200 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/jobs/__init__.py +6 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/settings.py +18 -12
- nautobot/core/settings.yaml +13 -7
- nautobot/core/signals.py +12 -1
- nautobot/core/tables.py +13 -6
- nautobot/core/templates/40x.html +1 -1
- nautobot/core/templates/500.html +2 -2
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/buttons/export.html +1 -1
- nautobot/core/templates/components/button/dropdown.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +2 -3
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +1 -1
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +4 -3
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- nautobot/core/templates/generic/object_bulk_remove.html +2 -2
- nautobot/core/templates/generic/object_bulk_update.html +5 -4
- nautobot/core/templates/generic/object_create.html +5 -4
- nautobot/core/templates/generic/object_import.html +2 -1
- nautobot/core/templates/generic/object_list.html +12 -4
- nautobot/core/templates/generic/object_notes.html +5 -3
- nautobot/core/templates/generic/object_retrieve.html +2 -3
- nautobot/core/templates/graphene/graphiql.html +7 -7
- nautobot/core/templates/home.html +1 -1
- nautobot/core/templates/import_success.html +2 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/created_updated.html +7 -3
- nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +1 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/nav_menu.html +2 -1
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +17 -3
- nautobot/core/templates/system_jobs/import_objects.html +1 -2
- nautobot/core/templates/utilities/confirmation_form.html +2 -2
- nautobot/core/templates/utilities/obj_table.html +10 -2
- nautobot/core/templates/utilities/render_field.html +7 -7
- nautobot/core/templates/utilities/render_jinja2.html +2 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +4 -4
- nautobot/core/templates/utilities/theme_preview.html +16 -3
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +52 -6
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +23 -10
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/views.py +4 -0
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +9 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +6 -7
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_forms.py +55 -13
- nautobot/core/tests/test_jobs.py +75 -1
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +76 -1
- nautobot/core/tests/test_patch_social_django.py +42 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +53 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +1 -1
- nautobot/core/tests/test_utils.py +31 -4
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +142 -10
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/object_detail.py +92 -12
- nautobot/core/urls.py +12 -1
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +17 -17
- nautobot/core/utils/lookup.py +3 -8
- nautobot/core/utils/module_loading.py +21 -0
- nautobot/core/utils/patch_social_django.py +128 -0
- nautobot/core/views/__init__.py +38 -1
- nautobot/core/views/generic.py +3 -3
- nautobot/core/views/mixins.py +15 -3
- nautobot/core/views/renderers.py +2 -0
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/apps.py +1 -5
- nautobot/data_validation/custom_validators.py +4 -4
- nautobot/data_validation/filters.py +1 -1
- nautobot/data_validation/forms.py +40 -0
- nautobot/data_validation/migrations/0001_initial.py +0 -7
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +0 -12
- nautobot/data_validation/models.py +16 -7
- nautobot/data_validation/navigation.py +8 -1
- nautobot/data_validation/tables.py +12 -5
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +2 -2
- nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
- nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
- nautobot/data_validation/tests/test_filters.py +8 -6
- nautobot/data_validation/tests/test_models.py +15 -0
- nautobot/data_validation/tests/test_views.py +190 -32
- nautobot/data_validation/urls.py +2 -5
- nautobot/data_validation/views.py +73 -40
- nautobot/dcim/api/serializers.py +0 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +16 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +2 -35
- nautobot/dcim/forms.py +1 -1
- nautobot/dcim/migrations/0078_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0079_device_name_data_migration.py +59 -0
- nautobot/dcim/models/device_components.py +81 -68
- nautobot/dcim/models/devices.py +13 -16
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +3 -0
- nautobot/dcim/tables/template_code.py +14 -14
- nautobot/dcim/templates/dcim/cable.html +2 -61
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_edit.html +2 -5
- nautobot/dcim/templates/dcim/cable_retrieve.html +61 -0
- nautobot/dcim/templates/dcim/cable_trace.html +1 -3
- nautobot/dcim/templates/dcim/cable_update.html +5 -0
- nautobot/dcim/templates/dcim/consoleport.html +6 -5
- nautobot/dcim/templates/dcim/consoleserverport.html +6 -5
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +1 -1
- nautobot/dcim/templates/dcim/device/consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/device/devicebays.html +1 -1
- nautobot/dcim/templates/dcim/device/frontports.html +1 -1
- nautobot/dcim/templates/dcim/device/interfaces.html +1 -1
- nautobot/dcim/templates/dcim/device/inventory.html +1 -1
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/modulebays.html +1 -1
- nautobot/dcim/templates/dcim/device/poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/device/powerports.html +1 -1
- nautobot/dcim/templates/dcim/device/rearports.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device/wireless.html +1 -1
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_component_add.html +2 -2
- nautobot/dcim/templates/dcim/device_create.html +5 -3
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/device_list.html +73 -10
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/frontport.html +9 -8
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +26 -6
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -242
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +5 -4
- nautobot/dcim/templates/dcim/rack_elevation_list.html +16 -4
- nautobot/dcim/templates/dcim/rack_retrieve.html +33 -15
- nautobot/dcim/templates/dcim/rearport.html +7 -6
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +14 -6
- nautobot/dcim/tests/integration/test_controller.py +1 -0
- nautobot/dcim/tests/test_api.py +8 -0
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +12 -6
- nautobot/dcim/tests/test_models.py +63 -4
- nautobot/dcim/tests/test_views.py +63 -22
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +547 -273
- nautobot/extras/api/views.py +9 -1
- nautobot/extras/choices.py +2 -13
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
- nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
- nautobot/extras/{filters/__init__.py → filters.py} +14 -46
- nautobot/extras/forms/forms.py +5 -13
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/management/__init__.py +9 -0
- nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
- nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
- nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +22 -13
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +59 -1
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +13 -0
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/navigation.py +13 -9
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -4
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +52 -68
- nautobot/extras/templates/extras/approval_dashboard.html +7 -5
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
- nautobot/extras/templates/extras/computedfield.html +1 -1
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -96
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
- nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
- nautobot/extras/templates/extras/inc/json_format.html +10 -3
- nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
- nautobot/extras/templates/extras/job.html +29 -12
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
- nautobot/extras/templates/extras/job_edit.html +52 -46
- nautobot/extras/templates/extras/job_list.html +29 -25
- nautobot/extras/templates/extras/marketplace.html +5 -9
- nautobot/extras/templates/extras/object_configcontext.html +1 -1
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/objectchange_retrieve.html +19 -37
- nautobot/extras/templates/extras/plugin_detail.html +26 -21
- nautobot/extras/templates/extras/plugins_list.html +16 -26
- nautobot/extras/templates/extras/role_retrieve.html +64 -0
- nautobot/extras/templates/extras/scheduledjob.html +4 -2
- nautobot/extras/templates/extras/secretsgroup.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +8 -7
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -1
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/test_api.py +27 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +35 -3
- nautobot/extras/tests/test_customfields.py +22 -13
- nautobot/extras/tests/test_customfields_filters.py +479 -0
- nautobot/extras/tests/test_dynamicgroups.py +39 -1
- nautobot/extras/tests/test_filters.py +21 -19
- nautobot/extras/tests/test_forms.py +18 -21
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +1 -0
- nautobot/extras/tests/test_models.py +13 -31
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_views.py +31 -30
- nautobot/extras/views.py +81 -19
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +27 -38
- nautobot/ipam/formfields.py +1 -1
- nautobot/ipam/forms.py +6 -3
- nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
- nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
- nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/navigation.py +3 -2
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/tables.py +13 -6
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
- nautobot/ipam/templates/ipam/ipaddress.html +14 -0
- nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/service.html +1 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +13 -6
- nautobot/ipam/tests/test_filters.py +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +43 -1
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +13 -14
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +46 -6
- nautobot/project-static/dist/css/nautobot.css +1 -1
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +1 -1
- nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
- nautobot/project-static/nautobot-icons/elements.svg +1 -1
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +1 -1
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +1 -1
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +1 -1
- nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
- nautobot/project-static/nautobot-icons/star.svg +1 -1
- nautobot/tenancy/api/serializers.py +1 -0
- nautobot/tenancy/api/views.py +2 -1
- nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
- nautobot/tenancy/navigation.py +3 -1
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -0
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +61 -28
- nautobot/users/templates/users/profile.html +45 -12
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/views.py +4 -2
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/navigation.py +3 -2
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_models.py +44 -4
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +209 -0
- nautobot/vpn/filters.py +233 -0
- nautobot/vpn/forms.py +486 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +527 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +380 -0
- nautobot/vpn/templates/vpn/vpnprofile.html +2 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +341 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +294 -0
- nautobot/vpn/tests/test_models.py +97 -0
- nautobot/vpn/tests/test_views.py +281 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +437 -0
- nautobot/wireless/navigation.py +3 -2
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +14 -14
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +417 -366
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/data_validation/template_content.py +0 -42
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
|
@@ -28,8 +28,8 @@ class ApprovalWorkflowDefinitionManager(BaseManager.from_queryset(RestrictedQuer
|
|
|
28
28
|
"""
|
|
29
29
|
content_type = ContentType.objects.get_for_model(model_instance)
|
|
30
30
|
|
|
31
|
-
# Get all workflow definitions for this content type, ordered by
|
|
32
|
-
workflow_definitions = self.get_queryset().filter(model_content_type=content_type).order_by("
|
|
31
|
+
# Get all workflow definitions for this content type, ordered by weight (highest wins)
|
|
32
|
+
workflow_definitions = self.get_queryset().filter(model_content_type=content_type).order_by("-weight")
|
|
33
33
|
|
|
34
34
|
for workflow_definition in workflow_definitions:
|
|
35
35
|
if not workflow_definition.model_constraints:
|
|
@@ -77,11 +77,12 @@ class ApprovalWorkflowDefinition(PrimaryModel):
|
|
|
77
77
|
default=dict,
|
|
78
78
|
help_text="Constraints to filter the objects that can be approved using this workflow.",
|
|
79
79
|
)
|
|
80
|
-
|
|
81
|
-
default=0, help_text="Determines workflow relevance when multiple apply.
|
|
80
|
+
weight = models.IntegerField(
|
|
81
|
+
default=0, help_text="Determines workflow relevance when multiple apply. Higher weight wins."
|
|
82
82
|
)
|
|
83
83
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
84
84
|
is_dynamic_group_associable = False
|
|
85
|
+
is_data_compliance_model = False
|
|
85
86
|
objects = ApprovalWorkflowDefinitionManager()
|
|
86
87
|
is_version_controlled = False
|
|
87
88
|
|
|
@@ -90,7 +91,7 @@ class ApprovalWorkflowDefinition(PrimaryModel):
|
|
|
90
91
|
|
|
91
92
|
verbose_name = "Approval Workflow Definition"
|
|
92
93
|
ordering = ["name"]
|
|
93
|
-
unique_together = [["model_content_type", "
|
|
94
|
+
unique_together = [["model_content_type", "weight"]]
|
|
94
95
|
|
|
95
96
|
def __str__(self):
|
|
96
97
|
"""Stringify instance."""
|
|
@@ -114,12 +115,13 @@ class ApprovalWorkflowStageDefinition(OrganizationalModel):
|
|
|
114
115
|
on_delete=models.CASCADE,
|
|
115
116
|
help_text="Approval workflow definition to which this stage belongs.",
|
|
116
117
|
)
|
|
117
|
-
|
|
118
|
-
help_text="The
|
|
118
|
+
sequence = models.PositiveIntegerField(
|
|
119
|
+
help_text="The sequence dictates the order in which this stage will need to be approved. The lower the number, the earlier it will be.",
|
|
119
120
|
)
|
|
120
121
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
|
|
121
122
|
min_approvers = models.PositiveIntegerField(
|
|
122
|
-
|
|
123
|
+
verbose_name="Minimum approvers",
|
|
124
|
+
help_text="Minimum number of approvers required to approve this stage.",
|
|
123
125
|
)
|
|
124
126
|
denial_message = models.CharField(
|
|
125
127
|
max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Message to show when the stage is denied."
|
|
@@ -128,18 +130,20 @@ class ApprovalWorkflowStageDefinition(OrganizationalModel):
|
|
|
128
130
|
to=Group,
|
|
129
131
|
related_name="approval_workflow_stage_definitions",
|
|
130
132
|
verbose_name="Group",
|
|
131
|
-
help_text="Group of users who are eligible to approve this stage.",
|
|
133
|
+
help_text="Group of users who are eligible to approve this stage. Only admin users can create new groups.",
|
|
132
134
|
on_delete=models.PROTECT,
|
|
133
135
|
)
|
|
134
136
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
135
137
|
is_version_controlled = False
|
|
136
138
|
|
|
139
|
+
is_data_compliance_model = False
|
|
140
|
+
|
|
137
141
|
class Meta:
|
|
138
142
|
"""Meta class for ApprovalWorkflowStage."""
|
|
139
143
|
|
|
140
144
|
verbose_name = "Approval Workflow Stage Definition"
|
|
141
|
-
unique_together = [["approval_workflow_definition", "name"], ["approval_workflow_definition", "
|
|
142
|
-
ordering = ["approval_workflow_definition", "
|
|
145
|
+
unique_together = [["approval_workflow_definition", "name"], ["approval_workflow_definition", "sequence"]]
|
|
146
|
+
ordering = ["approval_workflow_definition", "sequence"]
|
|
143
147
|
|
|
144
148
|
def __str__(self):
|
|
145
149
|
"""Stringify instance."""
|
|
@@ -195,6 +199,7 @@ class ApprovalWorkflow(OrganizationalModel):
|
|
|
195
199
|
user_name = models.CharField(max_length=150, editable=False, db_index=True)
|
|
196
200
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
197
201
|
|
|
202
|
+
is_data_compliance_model = False
|
|
198
203
|
is_version_controlled = False
|
|
199
204
|
|
|
200
205
|
class Meta:
|
|
@@ -225,7 +230,7 @@ class ApprovalWorkflow(OrganizationalModel):
|
|
|
225
230
|
"""
|
|
226
231
|
first_nonapproved_stage = (
|
|
227
232
|
self.approval_workflow_stages.exclude(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
228
|
-
.order_by("
|
|
233
|
+
.order_by("approval_workflow_stage_definition__sequence")
|
|
229
234
|
.first()
|
|
230
235
|
)
|
|
231
236
|
return first_nonapproved_stage
|
|
@@ -317,12 +322,14 @@ class ApprovalWorkflowStage(OrganizationalModel):
|
|
|
317
322
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
318
323
|
is_version_controlled = False
|
|
319
324
|
|
|
325
|
+
is_data_compliance_model = False
|
|
326
|
+
|
|
320
327
|
class Meta:
|
|
321
328
|
"""Meta class for ApprovalWorkflowStage."""
|
|
322
329
|
|
|
323
330
|
verbose_name = "Approval Workflow Stage"
|
|
324
331
|
unique_together = [["approval_workflow", "approval_workflow_stage_definition"]]
|
|
325
|
-
ordering = ["approval_workflow", "
|
|
332
|
+
ordering = ["approval_workflow", "approval_workflow_stage_definition__sequence"]
|
|
326
333
|
|
|
327
334
|
def __str__(self):
|
|
328
335
|
"""Stringify instance."""
|
|
@@ -506,6 +513,8 @@ class ApprovalWorkflowStageResponse(BaseModel):
|
|
|
506
513
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
507
514
|
is_version_controlled = False
|
|
508
515
|
|
|
516
|
+
is_data_compliance_model = False
|
|
517
|
+
|
|
509
518
|
class Meta:
|
|
510
519
|
"""Meta class for ApprovalWorkflowStageResponse."""
|
|
511
520
|
|
|
@@ -20,6 +20,7 @@ class ContactTeamSharedBase(PrimaryModel):
|
|
|
20
20
|
|
|
21
21
|
comments = models.TextField(blank=True)
|
|
22
22
|
is_contact_associable_model = False
|
|
23
|
+
is_data_compliance_model = False
|
|
23
24
|
|
|
24
25
|
class Meta:
|
|
25
26
|
abstract = True
|
|
@@ -94,6 +95,7 @@ class ContactAssociation(OrganizationalModel):
|
|
|
94
95
|
is_contact_associable_model = False
|
|
95
96
|
is_dynamic_group_associable_model = False
|
|
96
97
|
is_saved_view_model = False
|
|
98
|
+
is_data_compliance_model = False
|
|
97
99
|
|
|
98
100
|
class Meta:
|
|
99
101
|
unique_together = (
|
nautobot/extras/models/groups.py
CHANGED
|
@@ -76,6 +76,7 @@ class DynamicGroup(PrimaryModel):
|
|
|
76
76
|
|
|
77
77
|
objects = BaseManager.from_queryset(DynamicGroupQuerySet)()
|
|
78
78
|
is_dynamic_group_associable_model = False
|
|
79
|
+
is_data_compliance_model = False
|
|
79
80
|
|
|
80
81
|
clone_fields = ["content_type", "group_type", "filter", "tenant"]
|
|
81
82
|
|
|
@@ -628,6 +629,10 @@ class DynamicGroup(PrimaryModel):
|
|
|
628
629
|
else:
|
|
629
630
|
# Validate against the filterset's internal form validation.
|
|
630
631
|
filterset = self.filterset_class(self.filter) # pylint: disable=not-callable
|
|
632
|
+
# TODO: the below is more generous than one might expect. For example, passing a list of strings ["foo"]
|
|
633
|
+
# to a (single-input) CharFilter will quietly normalize the list to a string '["foo"]' instead of reporting
|
|
634
|
+
# any failure of is_valid(). We've had cases of such "should be invalid but isn't caught" DynamicGroups causing
|
|
635
|
+
# exceptions when trying to evaluate their membership; it would be good to be stricter here instead!
|
|
631
636
|
if not filterset.is_valid():
|
|
632
637
|
raise ValidationError(filterset.errors)
|
|
633
638
|
|
|
@@ -661,6 +666,22 @@ class DynamicGroup(PrimaryModel):
|
|
|
661
666
|
|
|
662
667
|
# TODO limit most changes to self.group_type as well.
|
|
663
668
|
|
|
669
|
+
def save(self, *args, update_cached_members=True, **kwargs):
|
|
670
|
+
"""
|
|
671
|
+
Save the DynamicGroup record.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
update_cached_members (bool): If True, (re)calculate the cached members set of the related group(s) immediately.
|
|
675
|
+
Note that this is potentially quite expensive if there will be a large change in the members set!
|
|
676
|
+
If False (recommended), you can call `self.update_cached_members()` explicitly when ready.
|
|
677
|
+
"""
|
|
678
|
+
super().save(*args, **kwargs)
|
|
679
|
+
|
|
680
|
+
if update_cached_members:
|
|
681
|
+
self.update_cached_members()
|
|
682
|
+
for ancestor in self.get_ancestors():
|
|
683
|
+
ancestor.update_cached_members()
|
|
684
|
+
|
|
664
685
|
def _generate_query_for_filter(self, filter_field, value):
|
|
665
686
|
"""
|
|
666
687
|
Return a `Q` object generated from a `filter_field` and `value`.
|
|
@@ -703,9 +724,12 @@ class DynamicGroup(PrimaryModel):
|
|
|
703
724
|
# "ams02"]}`, the value being a list of location names (`["ams01", "ams02"]`).
|
|
704
725
|
if value and isinstance(value, list) and isinstance(value[0], str) and not is_uuid(value[0]):
|
|
705
726
|
model_field = django_filters.utils.get_model_field(self._model, filter_field.field_name)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
727
|
+
if model_field is None:
|
|
728
|
+
gq_value = value
|
|
729
|
+
else:
|
|
730
|
+
related_model = model_field.related_model
|
|
731
|
+
lookup_kwargs = {f"{to_field_name}__in": value}
|
|
732
|
+
gq_value = related_model.objects.filter(**lookup_kwargs)
|
|
709
733
|
else:
|
|
710
734
|
gq_value = value
|
|
711
735
|
query |= filter_field.generate_query(gq_value)
|
|
@@ -1172,12 +1196,26 @@ class DynamicGroupMembership(BaseModel):
|
|
|
1172
1196
|
if self.group in self.parent_group.get_ancestors():
|
|
1173
1197
|
raise ValidationError({"group": "Cannot add ancestor as a child"})
|
|
1174
1198
|
|
|
1175
|
-
def save(self, *args, **kwargs):
|
|
1199
|
+
def save(self, *args, update_cached_members=True, **kwargs):
|
|
1200
|
+
"""
|
|
1201
|
+
Save the DynamicGroupMembership record.
|
|
1202
|
+
|
|
1203
|
+
Args:
|
|
1204
|
+
update_cached_members (bool): If True, (re)calculate the cached members set of the related group(s) immediately.
|
|
1205
|
+
Note that this is potentially quite expensive if there will be a large change in the members set!
|
|
1206
|
+
If False (recommended), you can call `self.parent_group.update_cached_members()` explicitly when ready.
|
|
1207
|
+
"""
|
|
1176
1208
|
# For backwards compatibility
|
|
1177
1209
|
if self.parent_group.group_type == DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER and not self.parent_group.filter:
|
|
1178
1210
|
self.parent_group.group_type = DynamicGroupTypeChoices.TYPE_DYNAMIC_SET
|
|
1179
1211
|
self.parent_group.save()
|
|
1180
|
-
|
|
1212
|
+
|
|
1213
|
+
super().save(*args, **kwargs)
|
|
1214
|
+
|
|
1215
|
+
if update_cached_members:
|
|
1216
|
+
self.parent_group.update_cached_members()
|
|
1217
|
+
for ancestor in self.parent_group.get_ancestors():
|
|
1218
|
+
ancestor.update_cached_members()
|
|
1181
1219
|
|
|
1182
1220
|
|
|
1183
1221
|
class StaticGroupAssociationManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
@@ -1218,6 +1256,7 @@ class StaticGroupAssociation(OrganizationalModel):
|
|
|
1218
1256
|
is_contact_associable_model = False
|
|
1219
1257
|
is_dynamic_group_associable_model = False
|
|
1220
1258
|
is_saved_view_model = False
|
|
1259
|
+
is_data_compliance_model = False
|
|
1221
1260
|
|
|
1222
1261
|
class Meta:
|
|
1223
1262
|
unique_together = [["dynamic_group", "associated_object_type", "associated_object_id"]]
|
nautobot/extras/models/jobs.py
CHANGED
|
@@ -15,7 +15,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
15
15
|
from django.core.exceptions import ValidationError
|
|
16
16
|
from django.core.validators import MinValueValidator
|
|
17
17
|
from django.db import models, transaction
|
|
18
|
-
from django.db.models import ProtectedError, signals
|
|
18
|
+
from django.db.models import Count, ProtectedError, Q, signals
|
|
19
19
|
from django.utils import timezone
|
|
20
20
|
from django.utils.functional import cached_property
|
|
21
21
|
from django_celery_beat.clockedschedule import clocked
|
|
@@ -252,6 +252,7 @@ class Job(PrimaryModel):
|
|
|
252
252
|
help_text="If set, the configured value will remain even if the underlying Job source code changes",
|
|
253
253
|
)
|
|
254
254
|
objects = BaseManager.from_queryset(JobQuerySet)()
|
|
255
|
+
is_data_compliance_model = False
|
|
255
256
|
|
|
256
257
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
|
|
257
258
|
|
|
@@ -439,6 +440,8 @@ class JobHook(OrganizationalModel):
|
|
|
439
440
|
type_delete = models.BooleanField(default=False, help_text="Call this job hook when a matching object is deleted.")
|
|
440
441
|
type_update = models.BooleanField(default=False, help_text="Call this job hook when a matching object is updated.")
|
|
441
442
|
|
|
443
|
+
is_data_compliance_model = False
|
|
444
|
+
|
|
442
445
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobhook.html"
|
|
443
446
|
|
|
444
447
|
class Meta:
|
|
@@ -529,6 +532,7 @@ class JobLogEntry(BaseModel):
|
|
|
529
532
|
absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
|
|
530
533
|
|
|
531
534
|
is_metadata_associable_model = False
|
|
535
|
+
is_data_compliance_model = False
|
|
532
536
|
|
|
533
537
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
|
|
534
538
|
hide_in_diff_view = True
|
|
@@ -580,6 +584,7 @@ class JobQueue(PrimaryModel):
|
|
|
580
584
|
)
|
|
581
585
|
|
|
582
586
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobqueue.html"
|
|
587
|
+
is_data_compliance_model = False
|
|
583
588
|
|
|
584
589
|
class Meta:
|
|
585
590
|
ordering = ["name"]
|
|
@@ -610,6 +615,7 @@ class JobQueueAssignment(BaseModel):
|
|
|
610
615
|
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name="job_queue_assignments")
|
|
611
616
|
job_queue = models.ForeignKey(JobQueue, on_delete=models.CASCADE, related_name="job_assignments")
|
|
612
617
|
is_metadata_associable_model = False
|
|
618
|
+
is_data_compliance_model = False
|
|
613
619
|
|
|
614
620
|
class Meta:
|
|
615
621
|
unique_together = ["job", "job_queue"]
|
|
@@ -678,11 +684,17 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
|
|
|
678
684
|
traceback = models.TextField(blank=True, null=True) # noqa: DJ001 # django-nullable-model-string-field -- TODO: can we remove null=True?
|
|
679
685
|
meta = models.JSONField(null=True, default=None, editable=False)
|
|
680
686
|
scheduled_job = models.ForeignKey(to="extras.ScheduledJob", on_delete=models.SET_NULL, null=True, blank=True)
|
|
687
|
+
debug_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
|
|
688
|
+
success_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
|
|
689
|
+
info_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
|
|
690
|
+
warning_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
|
|
691
|
+
error_log_count = models.PositiveIntegerField(blank=True, null=True, editable=False)
|
|
681
692
|
|
|
682
693
|
objects = JobResultManager()
|
|
683
694
|
|
|
684
695
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
|
|
685
696
|
hide_in_diff_view = True
|
|
697
|
+
is_data_compliance_model = False
|
|
686
698
|
|
|
687
699
|
def __init__(self, *args, **kwargs):
|
|
688
700
|
super().__init__(*args, **kwargs)
|
|
@@ -762,6 +774,30 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
|
|
|
762
774
|
|
|
763
775
|
set_status.alters_data = True
|
|
764
776
|
|
|
777
|
+
def count_logs_by_level(self):
|
|
778
|
+
"""Helper method to count JobLogEntries after a Job is run, or update these values when missing or changed."""
|
|
779
|
+
db_log_counts = self.job_log_entries.aggregate(
|
|
780
|
+
debug_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_DEBUG)),
|
|
781
|
+
success_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_SUCCESS)),
|
|
782
|
+
info_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_INFO)),
|
|
783
|
+
warning_log_count=Count("pk", filter=Q(log_level=LogLevelChoices.LOG_WARNING)),
|
|
784
|
+
error_log_count=Count(
|
|
785
|
+
"pk",
|
|
786
|
+
filter=Q(
|
|
787
|
+
log_level__in=[
|
|
788
|
+
LogLevelChoices.LOG_FAILURE,
|
|
789
|
+
LogLevelChoices.LOG_ERROR,
|
|
790
|
+
LogLevelChoices.LOG_CRITICAL,
|
|
791
|
+
]
|
|
792
|
+
),
|
|
793
|
+
),
|
|
794
|
+
)
|
|
795
|
+
self.debug_log_count = db_log_counts["debug_log_count"]
|
|
796
|
+
self.success_log_count = db_log_counts["success_log_count"]
|
|
797
|
+
self.info_log_count = db_log_counts["info_log_count"]
|
|
798
|
+
self.warning_log_count = db_log_counts["warning_log_count"]
|
|
799
|
+
self.error_log_count = db_log_counts["error_log_count"]
|
|
800
|
+
|
|
765
801
|
@classmethod
|
|
766
802
|
def execute_job(cls, *args, **kwargs):
|
|
767
803
|
"""
|
|
@@ -1006,6 +1042,18 @@ class JobResult(SavedViewMixin, BaseModel, CustomFieldModel):
|
|
|
1006
1042
|
|
|
1007
1043
|
log.alters_data = True
|
|
1008
1044
|
|
|
1045
|
+
def save(self, *args, **kwargs):
|
|
1046
|
+
"""When a JobResult is saved and in a terminal state, store missing log counts for summary."""
|
|
1047
|
+
if self.status in JobResultStatusChoices.READY_STATES and None in [
|
|
1048
|
+
self.debug_log_count,
|
|
1049
|
+
self.info_log_count,
|
|
1050
|
+
self.success_log_count,
|
|
1051
|
+
self.warning_log_count,
|
|
1052
|
+
self.error_log_count,
|
|
1053
|
+
]:
|
|
1054
|
+
self.count_logs_by_level()
|
|
1055
|
+
super().save(*args, **kwargs)
|
|
1056
|
+
|
|
1009
1057
|
|
|
1010
1058
|
#
|
|
1011
1059
|
# Job Button
|
|
@@ -1055,10 +1103,18 @@ class JobButton(ContactMixin, ChangeLoggedModel, DynamicGroupsModelMixin, NotesM
|
|
|
1055
1103
|
)
|
|
1056
1104
|
|
|
1057
1105
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/jobbutton.html"
|
|
1106
|
+
is_data_compliance_model = False
|
|
1058
1107
|
|
|
1059
1108
|
class Meta:
|
|
1060
1109
|
ordering = ["group_name", "weight", "name"]
|
|
1061
1110
|
|
|
1111
|
+
@property
|
|
1112
|
+
def button_class_css_class(self):
|
|
1113
|
+
"""Map self.button_class database value to the correct CSS class for buttons."""
|
|
1114
|
+
if self.button_class == ButtonClassChoices.CLASS_DEFAULT:
|
|
1115
|
+
return "secondary"
|
|
1116
|
+
return self.button_class
|
|
1117
|
+
|
|
1062
1118
|
def __str__(self):
|
|
1063
1119
|
return self.name
|
|
1064
1120
|
|
|
@@ -1081,6 +1137,7 @@ class ScheduledJobs(models.Model):
|
|
|
1081
1137
|
last_update = models.DateTimeField(null=False)
|
|
1082
1138
|
|
|
1083
1139
|
objects = ScheduledJobsManager()
|
|
1140
|
+
is_data_compliance_model = False
|
|
1084
1141
|
|
|
1085
1142
|
def __str__(self):
|
|
1086
1143
|
return str(self.ident)
|
|
@@ -1218,6 +1275,7 @@ class ScheduledJob(ApprovableModelMixin, BaseModel):
|
|
|
1218
1275
|
no_changes = False
|
|
1219
1276
|
|
|
1220
1277
|
documentation_static_path = "docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html"
|
|
1278
|
+
is_data_compliance_model = False
|
|
1221
1279
|
|
|
1222
1280
|
def __str__(self):
|
|
1223
1281
|
return f"{self.name}: {self.interval}"
|
nautobot/extras/models/mixins.py
CHANGED
|
@@ -251,3 +251,31 @@ class SavedViewMixin(models.Model):
|
|
|
251
251
|
abstract = True
|
|
252
252
|
|
|
253
253
|
is_saved_view_model = True
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class DataComplianceModelMixin:
|
|
257
|
+
"""
|
|
258
|
+
Adds a `get_data_compliance_url` that can be applied to instances.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
is_data_compliance_model = True
|
|
262
|
+
|
|
263
|
+
def get_data_compliance_url(self, api=False):
|
|
264
|
+
"""Return the data compliance URL for a given instance."""
|
|
265
|
+
# If is_data_compliance_model overridden should allow to opt out
|
|
266
|
+
if not self.is_data_compliance_model:
|
|
267
|
+
return None
|
|
268
|
+
route = get_route_for_model(self, "data-compliance", api=api)
|
|
269
|
+
|
|
270
|
+
# Iterate the pk-like fields and try to get a URL, or return None.
|
|
271
|
+
fields = ["pk", "slug"]
|
|
272
|
+
for field in fields:
|
|
273
|
+
if not hasattr(self, field):
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
return reverse(route, kwargs={field: getattr(self, field)})
|
|
278
|
+
except NoReverseMatch:
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
return None
|
nautobot/extras/models/models.py
CHANGED
|
@@ -353,6 +353,14 @@ class CustomLink(
|
|
|
353
353
|
)
|
|
354
354
|
new_window = models.BooleanField(help_text="Force link to open in a new window")
|
|
355
355
|
|
|
356
|
+
is_data_compliance_model = False
|
|
357
|
+
|
|
358
|
+
@property
|
|
359
|
+
def button_class_css_class(self):
|
|
360
|
+
if self.button_class == ButtonClassChoices.CLASS_DEFAULT:
|
|
361
|
+
return "secondary"
|
|
362
|
+
return self.button_class
|
|
363
|
+
|
|
356
364
|
class Meta:
|
|
357
365
|
ordering = ["group_name", "weight", "name"]
|
|
358
366
|
|
|
@@ -577,6 +585,7 @@ class FileAttachment(BaseModel):
|
|
|
577
585
|
mimetype = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
|
|
578
586
|
|
|
579
587
|
is_metadata_associable_model = False
|
|
588
|
+
is_data_compliance_model = False
|
|
580
589
|
|
|
581
590
|
natural_key_field_names = ["pk"]
|
|
582
591
|
|
|
@@ -638,6 +647,8 @@ class FileProxy(BaseModel):
|
|
|
638
647
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
|
639
648
|
job_result = models.ForeignKey(to=JobResult, null=True, blank=True, on_delete=models.CASCADE, related_name="files")
|
|
640
649
|
|
|
650
|
+
is_data_compliance_model = False
|
|
651
|
+
|
|
641
652
|
def __str__(self):
|
|
642
653
|
return self.name
|
|
643
654
|
|
|
@@ -877,6 +888,7 @@ class SavedView(BaseModel, ChangeLoggedModel):
|
|
|
877
888
|
is_shared = models.BooleanField(default=True)
|
|
878
889
|
|
|
879
890
|
documentation_static_path = "docs/user-guide/platform-functionality/savedview.html"
|
|
891
|
+
is_data_compliance_model = False
|
|
880
892
|
|
|
881
893
|
class Meta:
|
|
882
894
|
ordering = ["owner", "view", "name"]
|
|
@@ -905,6 +917,7 @@ class UserSavedViewAssociation(BaseModel):
|
|
|
905
917
|
user = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="saved_view_assignments")
|
|
906
918
|
view_name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
|
|
907
919
|
is_metadata_associable_model = False
|
|
920
|
+
is_data_compliance_model = False
|
|
908
921
|
|
|
909
922
|
class Meta:
|
|
910
923
|
unique_together = [["user", "view_name"]]
|
|
@@ -141,6 +141,7 @@ class SecretsGroupAssociation(BaseModel):
|
|
|
141
141
|
natural_key_field_names = ["secrets_group", "access_type", "secret_type", "secret"]
|
|
142
142
|
|
|
143
143
|
documentation_static_path = "docs/user-guide/platform-functionality/secret.html"
|
|
144
|
+
is_data_compliance_model = False
|
|
144
145
|
|
|
145
146
|
class Meta:
|
|
146
147
|
unique_together = (
|
|
@@ -7,7 +7,6 @@ from django.utils.hashable import make_hashable
|
|
|
7
7
|
|
|
8
8
|
from nautobot.core.models.fields import ForeignKeyLimitedByContentTypes
|
|
9
9
|
from nautobot.core.models.name_color_content_types import NameColorContentTypesModel
|
|
10
|
-
from nautobot.core.utils.deprecation import class_deprecated
|
|
11
10
|
from nautobot.extras.utils import extras_features, FeatureQuery
|
|
12
11
|
|
|
13
12
|
|
|
@@ -98,17 +97,3 @@ class StatusField(ForeignKeyLimitedByContentTypes):
|
|
|
98
97
|
f"get_{self.name}_color",
|
|
99
98
|
partialmethod(_get_FIELD_color, field=self),
|
|
100
99
|
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@class_deprecated(message="please directly declare `status = StatusField(...)` on your model instead")
|
|
104
|
-
class StatusModel(models.Model):
|
|
105
|
-
"""
|
|
106
|
-
Deprecated abstract base class for any model which may have statuses.
|
|
107
|
-
|
|
108
|
-
Just directly include a StatusField instead for any new models.
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
status = StatusField(null=True) # for backward compatibility
|
|
112
|
-
|
|
113
|
-
class Meta:
|
|
114
|
-
abstract = True
|
nautobot/extras/navigation.py
CHANGED
|
@@ -4,11 +4,13 @@ from nautobot.core.apps import (
|
|
|
4
4
|
NavMenuItem,
|
|
5
5
|
NavMenuTab,
|
|
6
6
|
)
|
|
7
|
+
from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
|
|
7
8
|
|
|
8
9
|
menu_items = (
|
|
9
10
|
NavMenuTab(
|
|
10
11
|
name="Approvals",
|
|
11
|
-
icon=
|
|
12
|
+
icon=NavigationIconChoices.APPROVAL_WORKFLOWS,
|
|
13
|
+
weight=NavigationWeightChoices.APPROVAL_WORKFLOWS,
|
|
12
14
|
groups=(
|
|
13
15
|
NavMenuGroup(
|
|
14
16
|
name="Approval Workflows",
|
|
@@ -38,7 +40,8 @@ menu_items = (
|
|
|
38
40
|
),
|
|
39
41
|
NavMenuTab(
|
|
40
42
|
name="Organization",
|
|
41
|
-
|
|
43
|
+
icon=NavigationIconChoices.ORGANIZATION,
|
|
44
|
+
weight=NavigationWeightChoices.ORGANIZATION,
|
|
42
45
|
groups=(
|
|
43
46
|
NavMenuGroup(
|
|
44
47
|
name="Contacts",
|
|
@@ -140,8 +143,8 @@ menu_items = (
|
|
|
140
143
|
),
|
|
141
144
|
NavMenuTab(
|
|
142
145
|
name="Secrets",
|
|
143
|
-
icon=
|
|
144
|
-
weight=
|
|
146
|
+
icon=NavigationIconChoices.SECRETS,
|
|
147
|
+
weight=NavigationWeightChoices.SECRETS,
|
|
145
148
|
groups=(
|
|
146
149
|
NavMenuGroup(
|
|
147
150
|
name="Secrets",
|
|
@@ -169,8 +172,8 @@ menu_items = (
|
|
|
169
172
|
),
|
|
170
173
|
NavMenuTab(
|
|
171
174
|
name="Jobs",
|
|
172
|
-
icon=
|
|
173
|
-
weight=
|
|
175
|
+
icon=NavigationIconChoices.JOBS,
|
|
176
|
+
weight=NavigationWeightChoices.JOBS,
|
|
174
177
|
groups=(
|
|
175
178
|
NavMenuGroup(
|
|
176
179
|
name="Jobs",
|
|
@@ -258,7 +261,8 @@ menu_items = (
|
|
|
258
261
|
),
|
|
259
262
|
NavMenuTab(
|
|
260
263
|
name="Extensibility",
|
|
261
|
-
|
|
264
|
+
icon=NavigationIconChoices.EXTENSIBILITY,
|
|
265
|
+
weight=NavigationWeightChoices.EXTENSIBILITY,
|
|
262
266
|
groups=(
|
|
263
267
|
NavMenuGroup(
|
|
264
268
|
name="Logging",
|
|
@@ -532,8 +536,8 @@ menu_items = (
|
|
|
532
536
|
),
|
|
533
537
|
NavMenuTab(
|
|
534
538
|
name="Apps",
|
|
535
|
-
icon=
|
|
536
|
-
weight=
|
|
539
|
+
icon=NavigationIconChoices.APPS,
|
|
540
|
+
weight=NavigationWeightChoices.APPS,
|
|
537
541
|
groups=(
|
|
538
542
|
NavMenuGroup(
|
|
539
543
|
name="General",
|