nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__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.
- nautobot/apps/choices.py +4 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +13 -1
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- 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 +2 -0
- nautobot/core/api/views.py +12 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
- nautobot/core/cli/migrate_deprecated_templates.py +227 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +62 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +35 -19
- nautobot/core/settings.yaml +17 -33
- 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/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/base_django.html +1 -2
- 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/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +6 -7
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +22 -9
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +6 -5
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- 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_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- 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 +4 -5
- nautobot/core/templates/graphene/graphiql.html +7 -8
- 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/footer.html +3 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +11 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +3 -9
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -11
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/rest_framework/api.html +103 -2
- 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 +37 -4
- nautobot/core/templates/utilities/theme_preview.html +19 -3
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +76 -18
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +41 -17
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -13
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +8 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +27 -28
- 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 +144 -3
- 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_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +62 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +123 -4
- nautobot/core/tests/test_utils.py +72 -5
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +70 -29
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +143 -27
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +143 -55
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +20 -9
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +28 -18
- nautobot/core/utils/lookup.py +49 -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 +45 -22
- nautobot/core/views/renderers.py +4 -3
- 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 +3 -14
- 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 +3 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +65 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
- nautobot/dcim/forms.py +90 -4
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +98 -64
- nautobot/dcim/models/devices.py +30 -20
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +18 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +15 -15
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_trace.html +0 -4
- nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
- nautobot/dcim/templates/dcim/consoleport.html +7 -6
- nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- 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.html +1 -1
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- 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 +10 -9
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +35 -7
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.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/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +6 -5
- nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
- nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
- nautobot/dcim/templates/dcim/rearport.html +8 -7
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
- nautobot/dcim/tests/integration/test_controller.py +4 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +194 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +55 -7
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +328 -4
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +132 -29
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +777 -397
- nautobot/extras/api/views.py +60 -45
- nautobot/extras/choices.py +2 -13
- nautobot/extras/datasources/git.py +3 -1
- 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} +33 -48
- nautobot/extras/forms/forms.py +14 -15
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +11 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- 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/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +33 -14
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +60 -4
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +23 -2
- 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/marketplace_manifest.yml +49 -1
- 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 -9
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +64 -68
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- 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/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- 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/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 -39
- nautobot/extras/templates/extras/plugin_detail.html +29 -24
- 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/secret_create.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_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +9 -10
- nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +90 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +59 -5
- 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 +57 -22
- 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 +51 -33
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +52 -112
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +164 -71
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +53 -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 +19 -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_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- 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 +36 -1
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +44 -2
- 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 +53 -11
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.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/forms.js +13 -0
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.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/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +112 -30
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- 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/filters.py +6 -1
- 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_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_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +45 -4
- nautobot/virtualization/views.py +4 -1
- 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 +219 -0
- nautobot/vpn/filters.py +234 -0
- nautobot/vpn/forms.py +487 -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 +535 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +383 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +336 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +293 -0
- nautobot/vpn/tests/test_models.py +147 -0
- nautobot/vpn/tests/test_views.py +300 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +495 -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.0rc1.dist-info}/METADATA +15 -15
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/template_content.py +0 -42
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
|
@@ -10,7 +10,9 @@ from nautobot.core.choices import ColorChoices
|
|
|
10
10
|
from nautobot.dcim import choices as dcim_choices
|
|
11
11
|
from nautobot.extras import choices as extras_choices
|
|
12
12
|
from nautobot.ipam import choices as ipam_choices
|
|
13
|
+
from nautobot.load_balancers import choices as load_balancer_choices
|
|
13
14
|
from nautobot.virtualization import choices as vm_choices
|
|
15
|
+
from nautobot.vpn import choices as vpn_choices
|
|
14
16
|
|
|
15
17
|
# List of 2-tuples of (model_path, choiceset)
|
|
16
18
|
# Add new mappings here as other models are supported.
|
|
@@ -34,8 +36,10 @@ STATUS_CHOICESET_MAP = {
|
|
|
34
36
|
"ipam.Prefix": ipam_choices.PrefixStatusChoices,
|
|
35
37
|
"ipam.VLAN": ipam_choices.VLANStatusChoices,
|
|
36
38
|
"ipam.VRF": ipam_choices.VRFStatusChoices,
|
|
39
|
+
"load_balancers.LoadBalancerPoolMember": load_balancer_choices.LoadBalancerPoolMemberStatusChoices,
|
|
37
40
|
"virtualization.VirtualMachine": vm_choices.VirtualMachineStatusChoices,
|
|
38
41
|
"virtualization.VMInterface": vm_choices.VMInterfaceStatusChoices,
|
|
42
|
+
"vpn.VPNTunnel": vpn_choices.VPNTunnelStatusChoices,
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
|
|
@@ -101,6 +105,7 @@ STATUS_DESCRIPTION_MAP = {
|
|
|
101
105
|
# Add new mappings here as other models are supported.
|
|
102
106
|
ROLE_CHOICESET_MAP = {
|
|
103
107
|
"extras.ContactAssociation": extras_choices.ContactAssociationRoleChoices,
|
|
108
|
+
"vpn.VPNTunnelEndpoint": vpn_choices.VPNTunnelEndpointRoleChoices,
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
# Map of role name -> default hex_color used when importing color choices in `export_roles_from_choiceset()`.
|
|
@@ -110,6 +115,9 @@ ROLE_COLOR_MAP = {
|
|
|
110
115
|
"Billing": ColorChoices.COLOR_GREEN,
|
|
111
116
|
"Support": ColorChoices.COLOR_YELLOW,
|
|
112
117
|
"On Site": ColorChoices.COLOR_BLACK,
|
|
118
|
+
"Hub": ColorChoices.COLOR_DARK_GREEN,
|
|
119
|
+
"Spoke": ColorChoices.COLOR_LIGHT_GREEN,
|
|
120
|
+
"Peer": ColorChoices.COLOR_ORANGE,
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
# Map of role name -> description used when importing role choices in `export_roles_from_choiceset()`.
|
|
@@ -118,6 +126,9 @@ ROLE_DESCRIPTION_MAP = {
|
|
|
118
126
|
"Billing": "Unit plays a billing role",
|
|
119
127
|
"Support": "Unit plays a support role",
|
|
120
128
|
"On Site": "Unit plays an on site role",
|
|
129
|
+
"Hub": "Unit plays a Hub role",
|
|
130
|
+
"Spoke": "Unit plays a Spoke role",
|
|
131
|
+
"Peer": "Unit plays a Peer role",
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
|
|
@@ -14,4 +14,7 @@ class Command(BaseCommand):
|
|
|
14
14
|
dynamic_groups = DynamicGroup.objects.all()
|
|
15
15
|
|
|
16
16
|
for dynamic_group in dynamic_groups:
|
|
17
|
-
|
|
17
|
+
try:
|
|
18
|
+
dynamic_group.update_cached_members()
|
|
19
|
+
except Exception as err:
|
|
20
|
+
self.stderr.write(self.style.ERROR(f"Error while refreshing {dynamic_group}: {err}"))
|
|
@@ -68,7 +68,7 @@ class Migration(migrations.Migration):
|
|
|
68
68
|
),
|
|
69
69
|
("name", models.CharField(max_length=255, unique=True)),
|
|
70
70
|
("model_constraints", models.JSONField(blank=True, default=dict)),
|
|
71
|
-
("
|
|
71
|
+
("weight", models.IntegerField(default=0)),
|
|
72
72
|
(
|
|
73
73
|
"model_content_type",
|
|
74
74
|
models.ForeignKey(
|
|
@@ -83,7 +83,7 @@ class Migration(migrations.Migration):
|
|
|
83
83
|
options={
|
|
84
84
|
"verbose_name": "Approval Workflow Definition",
|
|
85
85
|
"ordering": ["name"],
|
|
86
|
-
"unique_together": {("model_content_type", "
|
|
86
|
+
"unique_together": {("model_content_type", "weight")},
|
|
87
87
|
},
|
|
88
88
|
bases=(
|
|
89
89
|
nautobot.extras.models.mixins.DynamicGroupMixin,
|
|
@@ -122,7 +122,7 @@ class Migration(migrations.Migration):
|
|
|
122
122
|
],
|
|
123
123
|
options={
|
|
124
124
|
"verbose_name": "Approval Workflow Stage",
|
|
125
|
-
"ordering": ["approval_workflow", "
|
|
125
|
+
"ordering": ["approval_workflow", "approval_workflow_stage_definition__sequence"],
|
|
126
126
|
},
|
|
127
127
|
bases=(
|
|
128
128
|
nautobot.extras.models.mixins.DynamicGroupMixin,
|
|
@@ -179,7 +179,7 @@ class Migration(migrations.Migration):
|
|
|
179
179
|
"_custom_field_data",
|
|
180
180
|
models.JSONField(blank=True, default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
|
|
181
181
|
),
|
|
182
|
-
("
|
|
182
|
+
("sequence", models.PositiveIntegerField()),
|
|
183
183
|
("name", models.CharField(max_length=255)),
|
|
184
184
|
("min_approvers", models.PositiveIntegerField()),
|
|
185
185
|
("denial_message", models.CharField(blank=True, max_length=255)),
|
|
@@ -202,10 +202,10 @@ class Migration(migrations.Migration):
|
|
|
202
202
|
],
|
|
203
203
|
options={
|
|
204
204
|
"verbose_name": "Approval Workflow Stage Definition",
|
|
205
|
-
"ordering": ["approval_workflow_definition", "
|
|
205
|
+
"ordering": ["approval_workflow_definition", "sequence"],
|
|
206
206
|
"unique_together": {
|
|
207
207
|
("approval_workflow_definition", "name"),
|
|
208
|
-
("approval_workflow_definition", "
|
|
208
|
+
("approval_workflow_definition", "sequence"),
|
|
209
209
|
},
|
|
210
210
|
},
|
|
211
211
|
bases=(
|
nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Generated by Django 4.2.24 on 2025-10-04 00:03
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("extras", "0128_remove_job_approval_required_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="jobresult",
|
|
14
|
+
name="debug_log_count",
|
|
15
|
+
field=models.PositiveIntegerField(blank=True, editable=False, null=True),
|
|
16
|
+
),
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name="jobresult",
|
|
19
|
+
name="error_log_count",
|
|
20
|
+
field=models.PositiveIntegerField(blank=True, editable=False, null=True),
|
|
21
|
+
),
|
|
22
|
+
migrations.AddField(
|
|
23
|
+
model_name="jobresult",
|
|
24
|
+
name="info_log_count",
|
|
25
|
+
field=models.PositiveIntegerField(blank=True, editable=False, null=True),
|
|
26
|
+
),
|
|
27
|
+
migrations.AddField(
|
|
28
|
+
model_name="jobresult",
|
|
29
|
+
name="success_log_count",
|
|
30
|
+
field=models.PositiveIntegerField(blank=True, editable=False, null=True),
|
|
31
|
+
),
|
|
32
|
+
migrations.AddField(
|
|
33
|
+
model_name="jobresult",
|
|
34
|
+
name="warning_log_count",
|
|
35
|
+
field=models.PositiveIntegerField(blank=True, editable=False, null=True),
|
|
36
|
+
),
|
|
37
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Migration to populate job_results.*_log_counts on existing JobResults."""
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
from django.db.models import Count, Q
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _populate_log_counts(apps, *_):
|
|
8
|
+
JobResult = apps.get_model("extras", "JobResult")
|
|
9
|
+
job_results_missing_counts = JobResult.objects.filter(
|
|
10
|
+
Q(debug_log_count=None)
|
|
11
|
+
| Q(success_log_count=None)
|
|
12
|
+
| Q(info_log_count=None)
|
|
13
|
+
| Q(warning_log_count=None)
|
|
14
|
+
| Q(error_log_count=None)
|
|
15
|
+
)
|
|
16
|
+
for job_result in job_results_missing_counts:
|
|
17
|
+
db_log_counts = job_result.job_log_entries.aggregate(
|
|
18
|
+
debug_log_count=Count("pk", filter=Q(log_level="debug")),
|
|
19
|
+
success_log_count=Count("pk", filter=Q(log_level="success")),
|
|
20
|
+
info_log_count=Count("pk", filter=Q(log_level="info")),
|
|
21
|
+
warning_log_count=Count("pk", filter=Q(log_level="warning")),
|
|
22
|
+
error_log_count=Count(
|
|
23
|
+
"pk",
|
|
24
|
+
filter=Q(log_level__in=["failure", "error", "critical"]),
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
job_result.debug_log_count = db_log_counts["debug_log_count"]
|
|
28
|
+
job_result.success_log_count = db_log_counts["success_log_count"]
|
|
29
|
+
job_result.info_log_count = db_log_counts["info_log_count"]
|
|
30
|
+
job_result.warning_log_count = db_log_counts["warning_log_count"]
|
|
31
|
+
job_result.error_log_count = db_log_counts["error_log_count"]
|
|
32
|
+
job_result.save()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Migration(migrations.Migration):
|
|
36
|
+
dependencies = [
|
|
37
|
+
("extras", "0129_jobresult_debug_log_count_jobresult_error_log_count_and_more"),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
operations = [
|
|
41
|
+
migrations.RunPython(code=_populate_log_counts, reverse_code=migrations.RunPython.noop),
|
|
42
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 4.2.24 on 2025-09-26 19:15
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("dcim", "0074_alter_rack_u_height"),
|
|
9
|
+
("extras", "0130_jobresult_generate_log_entry_counts"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="configcontext",
|
|
15
|
+
name="device_families",
|
|
16
|
+
field=models.ManyToManyField(blank=True, related_name="+", to="dcim.devicefamily"),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -43,7 +43,7 @@ from .models import (
|
|
|
43
43
|
from .relationships import Relationship, RelationshipAssociation, RelationshipModel
|
|
44
44
|
from .roles import Role, RoleField
|
|
45
45
|
from .secrets import Secret, SecretsGroup, SecretsGroupAssociation
|
|
46
|
-
from .statuses import Status, StatusField
|
|
46
|
+
from .statuses import Status, StatusField
|
|
47
47
|
from .tags import Tag, TaggedItem
|
|
48
48
|
|
|
49
49
|
__all__ = (
|
|
@@ -103,7 +103,6 @@ __all__ = (
|
|
|
103
103
|
"StaticGroupAssociation",
|
|
104
104
|
"Status",
|
|
105
105
|
"StatusField",
|
|
106
|
-
"StatusModel",
|
|
107
106
|
"Tag",
|
|
108
107
|
"TaggedItem",
|
|
109
108
|
"Team",
|
|
@@ -4,7 +4,7 @@ from django.conf import settings
|
|
|
4
4
|
from django.contrib.auth.models import Group
|
|
5
5
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
6
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
|
-
from django.core.exceptions import ValidationError
|
|
7
|
+
from django.core.exceptions import FieldError, ValidationError
|
|
8
8
|
from django.db import models
|
|
9
9
|
from django.utils import timezone
|
|
10
10
|
|
|
@@ -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,12 +91,22 @@ 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."""
|
|
97
98
|
return self.name
|
|
98
99
|
|
|
100
|
+
def clean(self):
|
|
101
|
+
super().clean()
|
|
102
|
+
model_class = self.model_content_type.model_class()
|
|
103
|
+
if model_class is None:
|
|
104
|
+
raise ValidationError({"model_content_type": "Couldn't find corresponding model class. Is it installed?"})
|
|
105
|
+
try:
|
|
106
|
+
model_class.objects.filter(**self.model_constraints)
|
|
107
|
+
except (FieldError, AttributeError) as exc:
|
|
108
|
+
raise ValidationError({"model_constraints": f"Invalid query filter: {exc}"})
|
|
109
|
+
|
|
99
110
|
|
|
100
111
|
@extras_features(
|
|
101
112
|
"custom_links",
|
|
@@ -114,12 +125,13 @@ class ApprovalWorkflowStageDefinition(OrganizationalModel):
|
|
|
114
125
|
on_delete=models.CASCADE,
|
|
115
126
|
help_text="Approval workflow definition to which this stage belongs.",
|
|
116
127
|
)
|
|
117
|
-
|
|
118
|
-
help_text="The
|
|
128
|
+
sequence = models.PositiveIntegerField(
|
|
129
|
+
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
130
|
)
|
|
120
131
|
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
|
|
121
132
|
min_approvers = models.PositiveIntegerField(
|
|
122
|
-
|
|
133
|
+
verbose_name="Minimum approvers",
|
|
134
|
+
help_text="Minimum number of approvers required to approve this stage.",
|
|
123
135
|
)
|
|
124
136
|
denial_message = models.CharField(
|
|
125
137
|
max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Message to show when the stage is denied."
|
|
@@ -128,18 +140,20 @@ class ApprovalWorkflowStageDefinition(OrganizationalModel):
|
|
|
128
140
|
to=Group,
|
|
129
141
|
related_name="approval_workflow_stage_definitions",
|
|
130
142
|
verbose_name="Group",
|
|
131
|
-
help_text="Group of users who are eligible to approve this stage.",
|
|
143
|
+
help_text="Group of users who are eligible to approve this stage. Only admin users can create new groups.",
|
|
132
144
|
on_delete=models.PROTECT,
|
|
133
145
|
)
|
|
134
146
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
135
147
|
is_version_controlled = False
|
|
136
148
|
|
|
149
|
+
is_data_compliance_model = False
|
|
150
|
+
|
|
137
151
|
class Meta:
|
|
138
152
|
"""Meta class for ApprovalWorkflowStage."""
|
|
139
153
|
|
|
140
154
|
verbose_name = "Approval Workflow Stage Definition"
|
|
141
|
-
unique_together = [["approval_workflow_definition", "name"], ["approval_workflow_definition", "
|
|
142
|
-
ordering = ["approval_workflow_definition", "
|
|
155
|
+
unique_together = [["approval_workflow_definition", "name"], ["approval_workflow_definition", "sequence"]]
|
|
156
|
+
ordering = ["approval_workflow_definition", "sequence"]
|
|
143
157
|
|
|
144
158
|
def __str__(self):
|
|
145
159
|
"""Stringify instance."""
|
|
@@ -195,6 +209,7 @@ class ApprovalWorkflow(OrganizationalModel):
|
|
|
195
209
|
user_name = models.CharField(max_length=150, editable=False, db_index=True)
|
|
196
210
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
197
211
|
|
|
212
|
+
is_data_compliance_model = False
|
|
198
213
|
is_version_controlled = False
|
|
199
214
|
|
|
200
215
|
class Meta:
|
|
@@ -225,7 +240,7 @@ class ApprovalWorkflow(OrganizationalModel):
|
|
|
225
240
|
"""
|
|
226
241
|
first_nonapproved_stage = (
|
|
227
242
|
self.approval_workflow_stages.exclude(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
228
|
-
.order_by("
|
|
243
|
+
.order_by("approval_workflow_stage_definition__sequence")
|
|
229
244
|
.first()
|
|
230
245
|
)
|
|
231
246
|
return first_nonapproved_stage
|
|
@@ -317,12 +332,14 @@ class ApprovalWorkflowStage(OrganizationalModel):
|
|
|
317
332
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
318
333
|
is_version_controlled = False
|
|
319
334
|
|
|
335
|
+
is_data_compliance_model = False
|
|
336
|
+
|
|
320
337
|
class Meta:
|
|
321
338
|
"""Meta class for ApprovalWorkflowStage."""
|
|
322
339
|
|
|
323
340
|
verbose_name = "Approval Workflow Stage"
|
|
324
341
|
unique_together = [["approval_workflow", "approval_workflow_stage_definition"]]
|
|
325
|
-
ordering = ["approval_workflow", "
|
|
342
|
+
ordering = ["approval_workflow", "approval_workflow_stage_definition__sequence"]
|
|
326
343
|
|
|
327
344
|
def __str__(self):
|
|
328
345
|
"""Stringify instance."""
|
|
@@ -506,6 +523,8 @@ class ApprovalWorkflowStageResponse(BaseModel):
|
|
|
506
523
|
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
507
524
|
is_version_controlled = False
|
|
508
525
|
|
|
526
|
+
is_data_compliance_model = False
|
|
527
|
+
|
|
509
528
|
class Meta:
|
|
510
529
|
"""Meta class for ApprovalWorkflowStageResponse."""
|
|
511
530
|
|
|
@@ -154,6 +154,10 @@ class ObjectChange(BaseModel):
|
|
|
154
154
|
def __str__(self):
|
|
155
155
|
return f"{self.changed_object_type} {self.object_repr} {self.get_action_display().lower()} by {self.user_name}"
|
|
156
156
|
|
|
157
|
+
@property
|
|
158
|
+
def page_title(self):
|
|
159
|
+
return f"{self.object_repr} - {self.time.strftime('%Y-%m-%d %H:%M')}"
|
|
160
|
+
|
|
157
161
|
def save(self, *args, **kwargs):
|
|
158
162
|
# Record the user's name and the object's representation as static strings
|
|
159
163
|
if not self.user_name:
|
|
@@ -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}"
|
|
@@ -1355,7 +1413,6 @@ class ScheduledJob(ApprovableModelMixin, BaseModel):
|
|
|
1355
1413
|
job_queue: Optional[JobQueue] = None,
|
|
1356
1414
|
task_queue: Optional[str] = None, # deprecated!
|
|
1357
1415
|
ignore_singleton_lock: bool = False,
|
|
1358
|
-
validated_save: bool = True,
|
|
1359
1416
|
**job_kwargs,
|
|
1360
1417
|
):
|
|
1361
1418
|
"""
|
|
@@ -1445,8 +1502,7 @@ class ScheduledJob(ApprovableModelMixin, BaseModel):
|
|
|
1445
1502
|
crontab=crontab,
|
|
1446
1503
|
job_queue=job_queue,
|
|
1447
1504
|
)
|
|
1448
|
-
|
|
1449
|
-
scheduled_job.validated_save()
|
|
1505
|
+
scheduled_job.validated_save()
|
|
1450
1506
|
return scheduled_job
|
|
1451
1507
|
|
|
1452
1508
|
create_schedule.__func__.alters_data = True
|