nautobot 2.4.21__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 +2 -2
- nautobot/apps/filters.py +9 -9
- nautobot/apps/forms.py +2 -0
- nautobot/apps/models.py +7 -2
- nautobot/apps/ui.py +20 -1
- nautobot/apps/utils.py +2 -3
- nautobot/apps/views.py +7 -1
- nautobot/circuits/filters.py +8 -23
- nautobot/circuits/navigation.py +3 -1
- nautobot/circuits/templates/circuits/circuit_create.html +9 -9
- nautobot/circuits/templates/circuits/circuit_terminations_swap.html +2 -2
- nautobot/circuits/templates/circuits/circuittermination_create.html +24 -33
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +10 -10
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +13 -13
- nautobot/circuits/templates/circuits/inc/circuit_termination_header_extra_content.html +6 -6
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +3 -3
- nautobot/circuits/templates/circuits/inc/speed_widget.html +13 -13
- nautobot/circuits/templates/circuits/provider_create.html +9 -9
- nautobot/circuits/tests/integration/test_circuit.py +19 -19
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +3 -0
- nautobot/circuits/tests/integration/test_relationships.py +4 -12
- nautobot/circuits/views.py +0 -2
- nautobot/cloud/filters.py +1 -13
- nautobot/cloud/navigation.py +3 -1
- nautobot/cloud/templates/cloud/cloudnetwork_update.html +9 -9
- nautobot/cloud/templates/cloud/cloudservice_update.html +6 -6
- nautobot/core/api/fields.py +30 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +9 -2
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +58 -37
- nautobot/core/apps/__init__.py +6 -12
- nautobot/core/branching.py +83 -0
- nautobot/core/celery/__init__.py +11 -6
- nautobot/core/celery/backends.py +2 -0
- nautobot/core/celery/encoders.py +7 -0
- nautobot/core/celery/task.py +44 -0
- nautobot/core/checks.py +60 -0
- nautobot/core/cli/bootstrap_v3_to_v5.py +776 -0
- nautobot/core/constants.py +9 -0
- nautobot/core/context_processors.py +84 -0
- nautobot/core/filters.py +131 -2
- nautobot/core/forms/__init__.py +4 -2
- nautobot/core/forms/fields.py +10 -8
- nautobot/core/forms/forms.py +21 -9
- nautobot/core/forms/search.py +0 -15
- nautobot/core/forms/widgets.py +3 -2
- nautobot/core/graphql/__init__.py +8 -26
- nautobot/core/graphql/generators.py +16 -6
- nautobot/core/graphql/schema.py +1 -1
- nautobot/core/graphql/schema_init.py +1 -2
- nautobot/core/graphql/utils.py +7 -9
- nautobot/core/jobs/__init__.py +158 -0
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +17 -2
- nautobot/core/models/fields.py +3 -2
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/name_color_content_types.py +1 -1
- nautobot/core/models/ordering.py +7 -5
- nautobot/core/models/querysets.py +77 -2
- nautobot/core/models/tree_queries.py +6 -4
- nautobot/core/settings.py +30 -16
- nautobot/core/settings.yaml +13 -7
- nautobot/core/tables.py +114 -44
- nautobot/core/templates/403.html +1 -1
- nautobot/core/templates/403_csrf_failure.html +1 -1
- nautobot/core/templates/404.html +1 -1
- nautobot/core/templates/40x.html +8 -8
- nautobot/core/templates/500.html +10 -10
- nautobot/core/templates/about.html +13 -12
- nautobot/core/templates/admin/actions.html +1 -1
- nautobot/core/templates/admin/app_index.html +3 -3
- nautobot/core/templates/admin/base.html +45 -52
- nautobot/core/templates/admin/base_site.html +0 -9
- nautobot/core/templates/admin/change_form.html +5 -5
- nautobot/core/templates/admin/change_list.html +8 -12
- nautobot/core/templates/admin/change_list_results.html +3 -3
- nautobot/core/templates/admin/config/config.html +24 -24
- nautobot/core/templates/admin/delete_confirmation.html +5 -5
- nautobot/core/templates/admin/edit_inline/stacked.html +5 -5
- nautobot/core/templates/admin/edit_inline/tabular.html +3 -3
- nautobot/core/templates/admin/includes/fieldset.html +15 -15
- nautobot/core/templates/admin/index.html +8 -8
- nautobot/core/templates/admin/submit_line.html +5 -5
- nautobot/core/templates/base_django.html +36 -32
- nautobot/core/templates/buttons/add.html +1 -1
- nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +2 -2
- nautobot/core/templates/buttons/export.html +17 -18
- nautobot/core/templates/buttons/job_import.html +2 -2
- nautobot/core/templates/components/breadcrumbs.html +19 -17
- nautobot/core/templates/components/button/dropdown.html +7 -5
- nautobot/core/templates/components/echarts.html +2 -0
- nautobot/core/templates/components/layout/one_over_two.html +3 -3
- nautobot/core/templates/components/layout/two_over_one.html +3 -3
- nautobot/core/templates/components/panel/body_content_data_table.html +2 -2
- nautobot/core/templates/components/panel/body_content_tags.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_generic.html +4 -2
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/body_wrapper_key_value_table.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_table.html +4 -2
- nautobot/core/templates/components/panel/footer_contacts_table.html +4 -4
- nautobot/core/templates/components/panel/footer_content_table.html +3 -3
- nautobot/core/templates/components/panel/grouping_toggle.html +12 -11
- nautobot/core/templates/components/panel/header_extra_content_table.html +2 -11
- nautobot/core/templates/components/panel/panel.html +6 -3
- nautobot/core/templates/components/panel/stats_panel_body.html +9 -7
- nautobot/core/templates/components/tab/content_wrapper.html +29 -1
- nautobot/core/templates/components/tab/label_wrapper.html +10 -2
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +11 -4
- nautobot/core/templates/echarts/echarts.html +20 -0
- nautobot/core/templates/exceptions/import_error.html +2 -2
- nautobot/core/templates/exceptions/permission_error.html +1 -1
- nautobot/core/templates/exceptions/programming_error.html +2 -2
- nautobot/core/templates/generic/object_bulk_add_component.html +29 -20
- nautobot/core/templates/generic/object_bulk_create.html +87 -75
- nautobot/core/templates/generic/object_bulk_destroy.html +35 -37
- nautobot/core/templates/generic/object_bulk_remove.html +30 -26
- nautobot/core/templates/generic/object_bulk_rename.html +53 -40
- nautobot/core/templates/generic/object_bulk_update.html +36 -29
- nautobot/core/templates/generic/object_create.html +40 -27
- nautobot/core/templates/generic/object_import.html +36 -24
- nautobot/core/templates/generic/object_list.html +279 -215
- nautobot/core/templates/generic/object_notes.html +21 -11
- nautobot/core/templates/generic/object_retrieve.html +161 -213
- nautobot/core/templates/graphene/graphiql.html +113 -60
- nautobot/core/templates/home.html +164 -87
- nautobot/core/templates/import_success.html +3 -2
- nautobot/core/templates/inc/ajax_loader.html +1 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +25 -13
- nautobot/core/templates/inc/created_updated.html +12 -7
- nautobot/core/templates/inc/custom_fields/panel_data.html +28 -16
- nautobot/core/templates/inc/custom_fields_panel.html +3 -3
- nautobot/core/templates/inc/dynamic_groups_panel.html +3 -3
- nautobot/core/templates/inc/extras_features_edit_form_fields.html +15 -15
- nautobot/core/templates/inc/footer.html +90 -40
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +75 -0
- nautobot/core/templates/inc/header_banners.html +17 -0
- nautobot/core/templates/inc/header_messages.html +6 -0
- nautobot/core/templates/inc/image_attachments.html +9 -9
- nautobot/core/templates/inc/javascript.html +7 -24
- nautobot/core/templates/inc/media.html +4 -29
- nautobot/core/templates/inc/modal.html +2 -2
- nautobot/core/templates/inc/nav_favorites.html +27 -0
- nautobot/core/templates/inc/nav_menu.html +150 -108
- nautobot/core/templates/inc/object_details_advanced_panel.html +84 -71
- nautobot/core/templates/inc/page_title.html +23 -0
- nautobot/core/templates/inc/paginator.html +39 -28
- nautobot/core/templates/inc/relationships/panel_override.html +3 -3
- nautobot/core/templates/inc/relationships_panel.html +3 -3
- nautobot/core/templates/inc/relationships_table_rows.html +1 -1
- nautobot/core/templates/inc/search_panel.html +22 -16
- nautobot/core/templates/inc/table.html +61 -36
- nautobot/core/templates/inc/tenancy_form_panel.html +3 -3
- nautobot/core/templates/login.html +17 -59
- nautobot/core/templates/modals/modal_theme.html +12 -23
- nautobot/core/templates/nautobot_config.py.j2 +6 -5
- nautobot/core/templates/panel_table.html +8 -12
- nautobot/core/templates/redoc_ui.html +80 -0
- nautobot/core/templates/rest_framework/api.html +43 -21
- nautobot/core/templates/search.html +12 -13
- nautobot/core/templates/swagger_ui.html +19 -4
- nautobot/core/templates/system_jobs/import_objects.html +70 -58
- nautobot/core/templates/template.css +0 -6
- nautobot/core/templates/utilities/comment_form.html +34 -0
- nautobot/core/templates/utilities/confirmation_form.html +17 -9
- nautobot/core/templates/utilities/obj_table.html +19 -11
- nautobot/core/templates/utilities/render_field.html +27 -21
- nautobot/core/templates/utilities/render_jinja2.html +22 -25
- nautobot/core/templates/utilities/templatetags/advanced_filter_indicator.html +8 -0
- nautobot/core/templates/utilities/templatetags/badge.html +1 -1
- nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +2 -3
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +482 -0
- nautobot/core/templates/utilities/templatetags/modal_form_as_dialog.html +14 -18
- nautobot/core/templates/utilities/templatetags/saved_view_modal.html +11 -11
- nautobot/core/templates/utilities/templatetags/table_config_form.html +51 -24
- nautobot/core/templates/utilities/templatetags/tag.html +1 -1
- nautobot/core/templates/utilities/templatetags/utilization_graph.html +3 -3
- nautobot/core/templates/utilities/theme_preview.html +829 -566
- nautobot/core/templates/utilities/worker_status.html +42 -41
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templates/widgets/sluginput.html +2 -2
- nautobot/core/templatetags/buttons.py +38 -40
- nautobot/core/templatetags/helpers.py +105 -28
- nautobot/core/templatetags/ui_framework.py +17 -0
- nautobot/core/testing/api.py +76 -12
- nautobot/core/testing/filters.py +11 -27
- nautobot/core/testing/integration.py +128 -10
- nautobot/core/testing/mixins.py +7 -4
- nautobot/core/testing/utils.py +28 -5
- nautobot/core/testing/views.py +125 -27
- nautobot/core/tests/integration/test_app_home.py +39 -35
- nautobot/core/tests/integration/test_app_navbar.py +60 -67
- nautobot/core/tests/integration/test_filters.py +123 -55
- nautobot/core/tests/integration/test_general_functionality.py +1 -1
- nautobot/core/tests/integration/test_home.py +10 -18
- nautobot/core/tests/integration/test_import_objects_ui.py +2 -9
- nautobot/core/tests/integration/test_navbar.py +41 -16
- nautobot/core/tests/integration/test_swagger.py +1 -7
- nautobot/core/tests/integration/test_theme.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +6 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_branching.py +154 -0
- nautobot/core/tests/test_breadcrumbs.py +7 -8
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_commands.py +0 -41
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_csv.py +4 -7
- nautobot/core/tests/test_filters.py +326 -318
- nautobot/core/tests/test_forms.py +19 -30
- nautobot/core/tests/test_graphql.py +67 -57
- nautobot/core/tests/test_models.py +1 -1
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +78 -10
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +61 -21
- nautobot/core/tests/test_templatetags_ui_framework.py +36 -18
- nautobot/core/tests/test_ui.py +207 -2
- nautobot/core/tests/test_utils.py +147 -2
- nautobot/core/tests/test_views.py +201 -64
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +2 -12
- nautobot/core/ui/choices.py +190 -0
- nautobot/core/ui/constants.py +86 -0
- nautobot/core/ui/echarts.py +474 -0
- nautobot/core/ui/nav.py +5 -1
- nautobot/core/ui/object_detail.py +180 -16
- nautobot/core/urls.py +13 -1
- nautobot/core/utils/cache.py +71 -0
- nautobot/core/utils/data.py +8 -5
- nautobot/core/utils/filtering.py +8 -2
- nautobot/core/utils/git.py +3 -3
- nautobot/core/utils/lookup.py +87 -13
- nautobot/core/utils/migrations.py +22 -0
- nautobot/core/utils/module_loading.py +26 -0
- nautobot/core/utils/permissions.py +9 -5
- nautobot/core/views/__init__.py +114 -63
- nautobot/core/views/generic.py +34 -27
- nautobot/core/views/mixins.py +49 -27
- nautobot/core/views/renderers.py +3 -5
- nautobot/core/views/utils.py +10 -5
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/__init__.py +0 -0
- nautobot/data_validation/api/__init__.py +1 -0
- nautobot/data_validation/api/serializers.py +80 -0
- nautobot/data_validation/api/urls.py +20 -0
- nautobot/data_validation/api/views.py +44 -0
- nautobot/data_validation/apps.py +18 -0
- nautobot/data_validation/custom_validators.py +330 -0
- nautobot/data_validation/filters.py +133 -0
- nautobot/data_validation/form_mixin.py +25 -0
- nautobot/data_validation/forms.py +342 -0
- nautobot/data_validation/migrations/0001_initial.py +224 -0
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +324 -0
- nautobot/data_validation/migrations/__init__.py +0 -0
- nautobot/data_validation/models.py +361 -0
- nautobot/data_validation/navigation.py +74 -0
- nautobot/data_validation/signals.py +30 -0
- nautobot/data_validation/tables.py +259 -0
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +1 -0
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +11 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +20 -0
- nautobot/data_validation/tests/migrations/__init__.py +0 -0
- nautobot/data_validation/tests/migrations/test_migrations.py +489 -0
- nautobot/data_validation/tests/test_api.py +238 -0
- nautobot/data_validation/tests/test_custom_validators.py +423 -0
- nautobot/data_validation/tests/test_data_compliance_rules.py +85 -0
- nautobot/data_validation/tests/test_filters.py +240 -0
- nautobot/data_validation/tests/test_form_mixin.py +115 -0
- nautobot/data_validation/tests/test_models.py +393 -0
- nautobot/data_validation/tests/test_views.py +435 -0
- nautobot/data_validation/urls.py +21 -0
- nautobot/data_validation/views.py +227 -0
- nautobot/dcim/api/serializers.py +10 -13
- nautobot/dcim/api/urls.py +2 -0
- nautobot/dcim/api/views.py +7 -0
- 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} +70 -157
- nautobot/dcim/forms.py +12 -6
- nautobot/dcim/graphql/types.py +1 -0
- nautobot/dcim/migrations/0075_add_deviceclusterassignment.py +52 -0
- nautobot/dcim/migrations/0076_device_cluster_to_clusters_data_migration.py +40 -0
- nautobot/dcim/migrations/0077_remove_device_cluster.py +14 -0
- 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/__init__.py +2 -0
- nautobot/dcim/models/device_components.py +3 -1
- nautobot/dcim/models/devices.py +115 -51
- nautobot/dcim/navigation.py +7 -3
- nautobot/dcim/querysets.py +6 -0
- nautobot/dcim/signals.py +19 -0
- nautobot/dcim/tables/devices.py +9 -5
- nautobot/dcim/tables/template_code.py +191 -102
- nautobot/dcim/templates/dcim/cable.html +1 -1
- nautobot/dcim/templates/dcim/cable_connect.html +62 -146
- nautobot/dcim/templates/dcim/cable_retrieve.html +10 -10
- nautobot/dcim/templates/dcim/cable_trace.html +15 -17
- nautobot/dcim/templates/dcim/console_port_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/consoleport.html +18 -17
- nautobot/dcim/templates/dcim/consoleserverport.html +18 -17
- nautobot/dcim/templates/dcim/controller_create.html +12 -8
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +1 -1
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +6 -6
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/device/config.html +17 -19
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +4 -4
- nautobot/dcim/templates/dcim/device/status.html +20 -20
- nautobot/dcim/templates/dcim/device_component_add.html +24 -15
- nautobot/dcim/templates/dcim/device_create.html +120 -120
- nautobot/dcim/templates/dcim/device_list.html +75 -12
- nautobot/dcim/templates/dcim/devicebay.html +7 -7
- nautobot/dcim/templates/dcim/devicebay_populate.html +29 -23
- nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +6 -6
- nautobot/dcim/templates/dcim/devicetype.html +1 -1
- nautobot/dcim/templates/dcim/devicetype_component_add.html +25 -19
- nautobot/dcim/templates/dcim/devicetype_list.html +4 -4
- nautobot/dcim/templates/dcim/devicetype_update.html +9 -9
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +3 -3
- nautobot/dcim/templates/dcim/frontport.html +21 -20
- nautobot/dcim/templates/dcim/inc/cable_form.html +7 -7
- nautobot/dcim/templates/dcim/inc/cable_termination.html +1 -1
- nautobot/dcim/templates/dcim/inc/cable_toggle_buttons.html +18 -9
- nautobot/dcim/templates/dcim/inc/detail_softwareversion_softwareimagefile_rows.html +1 -1
- nautobot/dcim/templates/dcim/inc/device_interface_filter.html +1 -1
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/homepage_connections.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +10 -10
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/interface.html +42 -22
- nautobot/dcim/templates/dcim/interface_connection_list.html +2 -2
- nautobot/dcim/templates/dcim/interface_edit.html +26 -11
- nautobot/dcim/templates/dcim/interfaceredundancygroupassociation_create.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem.html +3 -3
- nautobot/dcim/templates/dcim/inventoryitem_add.html +21 -10
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +6 -4
- nautobot/dcim/templates/dcim/location.html +1 -1
- nautobot/dcim/templates/dcim/location_migrate_data_to_contact.html +24 -18
- nautobot/dcim/templates/dcim/location_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/location_update.html +9 -9
- nautobot/dcim/templates/dcim/locationtype.html +0 -1
- nautobot/dcim/templates/dcim/module/base.html +67 -27
- nautobot/dcim/templates/dcim/module_consoleports.html +13 -15
- nautobot/dcim/templates/dcim/module_consoleserverports.html +13 -15
- nautobot/dcim/templates/dcim/module_frontports.html +13 -15
- nautobot/dcim/templates/dcim/module_interfaces.html +14 -16
- nautobot/dcim/templates/dcim/module_list.html +59 -10
- nautobot/dcim/templates/dcim/module_modulebays.html +12 -14
- nautobot/dcim/templates/dcim/module_poweroutlets.html +13 -15
- nautobot/dcim/templates/dcim/module_powerports.html +13 -15
- nautobot/dcim/templates/dcim/module_rearports.html +13 -15
- nautobot/dcim/templates/dcim/module_retrieve.html +3 -3
- nautobot/dcim/templates/dcim/module_update.html +15 -9
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -93
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +7 -7
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +74 -35
- nautobot/dcim/templates/dcim/platform_create.html +9 -9
- nautobot/dcim/templates/dcim/power_port_connection_list.html +3 -3
- nautobot/dcim/templates/dcim/powerfeed.html +1 -1
- nautobot/dcim/templates/dcim/powerfeed_edit.html +15 -15
- nautobot/dcim/templates/dcim/poweroutlet.html +13 -13
- nautobot/dcim/templates/dcim/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +17 -16
- nautobot/dcim/templates/dcim/rack.html +1 -1
- nautobot/dcim/templates/dcim/rack_elevation.html +2 -2
- nautobot/dcim/templates/dcim/rack_elevation_list.html +21 -9
- nautobot/dcim/templates/dcim/rack_retrieve.html +75 -57
- nautobot/dcim/templates/dcim/rack_update.html +14 -14
- nautobot/dcim/templates/dcim/rackreservation.html +1 -1
- nautobot/dcim/templates/dcim/rackreservation_edit.html +6 -6
- nautobot/dcim/templates/dcim/rearport.html +19 -18
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/trace/circuit.html +1 -1
- nautobot/dcim/templates/dcim/trace/device.html +1 -1
- nautobot/dcim/templates/dcim/trace/powerpanel.html +1 -1
- nautobot/dcim/templates/dcim/trace/termination.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +25 -16
- nautobot/dcim/templates/dcim/virtualchassis_create.html +6 -6
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_update.html +36 -22
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +9 -9
- nautobot/dcim/tests/integration/test_controller.py +6 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +7 -7
- nautobot/dcim/tests/integration/test_create_device.py +9 -9
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +7 -2
- nautobot/dcim/tests/integration/test_fileinputpicker.py +5 -7
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +2 -0
- nautobot/dcim/tests/integration/test_module_bay_position.py +4 -1
- nautobot/dcim/tests/test_api.py +86 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +159 -110
- nautobot/dcim/tests/test_graphql.py +32 -36
- nautobot/dcim/tests/test_jobs.py +1 -1
- nautobot/dcim/tests/test_models.py +229 -1
- nautobot/dcim/tests/test_views.py +31 -20
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +77 -41
- nautobot/extras/api/serializers.py +83 -19
- nautobot/extras/api/urls.py +7 -0
- nautobot/extras/api/views.py +243 -140
- nautobot/extras/choices.py +34 -13
- nautobot/extras/constants.py +1 -1
- nautobot/extras/context_managers.py +26 -26
- nautobot/extras/datasources/git.py +22 -0
- nautobot/extras/datasources/registry.py +3 -0
- nautobot/extras/exceptions.py +5 -0
- nautobot/extras/factory.py +11 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +4 -3
- nautobot/extras/{filters/__init__.py → filters.py} +203 -58
- nautobot/extras/forms/base.py +2 -1
- nautobot/extras/forms/forms.py +225 -20
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/homepage.py +21 -2
- nautobot/extras/jobs.py +2 -8
- nautobot/extras/jobs_ui.py +2 -2
- nautobot/extras/management/__init__.py +9 -0
- nautobot/extras/managers.py +31 -22
- nautobot/extras/migrations/0126_approval_workflow_pre_check.py +58 -0
- nautobot/extras/migrations/0127_approval_workflow_models.py +266 -0
- nautobot/extras/migrations/0128_remove_job_approval_required_and_more.py +29 -0
- 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 +14 -3
- nautobot/extras/models/approvals.py +556 -0
- nautobot/extras/models/change_logging.py +1 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/customfields.py +57 -22
- nautobot/extras/models/datasources.py +21 -0
- nautobot/extras/models/groups.py +2 -0
- nautobot/extras/models/jobs.py +122 -39
- nautobot/extras/models/metadata.py +2 -3
- nautobot/extras/models/mixins.py +129 -1
- nautobot/extras/models/models.py +22 -14
- nautobot/extras/models/relationships.py +47 -10
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/models/tags.py +1 -1
- nautobot/extras/navigation.py +42 -15
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +1 -23
- nautobot/extras/plugins/tables.py +8 -6
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/validators.py +10 -10
- nautobot/extras/plugins/views.py +1 -5
- nautobot/extras/querysets.py +17 -21
- nautobot/extras/signals.py +23 -8
- nautobot/extras/tables.py +420 -99
- nautobot/extras/templates/extras/approval_dashboard.html +15 -0
- nautobot/extras/templates/extras/approval_workflow/approve.html +11 -0
- nautobot/extras/templates/extras/approval_workflow/comment.html +9 -0
- nautobot/extras/templates/extras/approval_workflow/deny.html +10 -0
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +77 -0
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +29 -0
- nautobot/extras/templates/extras/configcontext_update.html +12 -12
- nautobot/extras/templates/extras/configcontextschema.html +1 -1
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +9 -9
- nautobot/extras/templates/extras/configcontextschema_update.html +6 -6
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/customfield_update.html +12 -12
- nautobot/extras/templates/extras/dynamicgroup.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_edit.html +1 -1
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +17 -17
- nautobot/extras/templates/extras/dynamicgroup_update.html +24 -24
- nautobot/extras/templates/extras/externalintegration_update.html +6 -6
- nautobot/extras/templates/extras/gitrepository.html +1 -1
- nautobot/extras/templates/extras/gitrepository_object_edit.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +1 -1
- nautobot/extras/templates/extras/gitrepository_retrieve.html +12 -12
- nautobot/extras/templates/extras/gitrepository_update.html +25 -7
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +1 -1
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +38 -0
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +14 -13
- nautobot/extras/templates/extras/inc/configcontext_format.html +11 -4
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +7 -7
- nautobot/extras/templates/extras/inc/job_label.html +5 -5
- nautobot/extras/templates/extras/inc/job_table.html +23 -10
- nautobot/extras/templates/extras/inc/job_tiles.html +33 -21
- nautobot/extras/templates/extras/inc/jobresult.html +6 -6
- nautobot/extras/templates/extras/inc/json_format.html +11 -4
- nautobot/extras/templates/extras/inc/object_contact_header.html +6 -6
- nautobot/extras/templates/extras/inc/overridable_field.html +16 -15
- nautobot/extras/templates/extras/inc/panel_approvalworkflowstage.html +34 -0
- nautobot/extras/templates/extras/inc/panel_changelog.html +9 -9
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +8 -6
- nautobot/extras/templates/extras/inc/tags_panel.html +3 -3
- nautobot/extras/templates/extras/job.html +154 -155
- nautobot/extras/templates/extras/job_approval_confirmation.html +4 -27
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -1
- nautobot/extras/templates/extras/job_detail.html +1 -1
- nautobot/extras/templates/extras/job_edit.html +69 -64
- nautobot/extras/templates/extras/job_list.html +37 -60
- nautobot/extras/templates/extras/jobresult.html +1 -1
- nautobot/extras/templates/extras/jobresult_retrieve.html +17 -17
- nautobot/extras/templates/extras/marketplace.html +62 -71
- nautobot/extras/templates/extras/metadatatype_create.html +9 -9
- nautobot/extras/templates/extras/note.html +1 -1
- nautobot/extras/templates/extras/object_approvalworkflow.html +36 -0
- nautobot/extras/templates/extras/object_assign_contact_or_team.html +16 -7
- nautobot/extras/templates/extras/object_configcontext.html +20 -20
- nautobot/extras/templates/extras/object_new_contact.html +6 -6
- nautobot/extras/templates/extras/object_new_team.html +6 -6
- nautobot/extras/templates/extras/objectchange.html +1 -1
- nautobot/extras/templates/extras/objectchange_retrieve.html +37 -56
- nautobot/extras/templates/extras/plugin_detail.html +40 -41
- nautobot/extras/templates/extras/plugins_list.html +23 -38
- nautobot/extras/templates/extras/plugins_tiles.html +28 -28
- nautobot/extras/templates/extras/role_retrieve.html +112 -48
- nautobot/extras/templates/extras/scheduledjob.html +25 -28
- nautobot/extras/templates/extras/secret_create.html +11 -11
- nautobot/extras/templates/extras/secretsgroup_update.html +6 -6
- nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +3 -3
- nautobot/extras/templates/extras/status.html +1 -1
- nautobot/extras/templates/extras/tag.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +3 -3
- nautobot/extras/templates/extras/templatetags/log_level.html +1 -1
- nautobot/extras/templates/extras/templatetags/plugin_object_detail_tabs.html +2 -2
- nautobot/extras/templates/extras/webhook.html +12 -12
- nautobot/extras/templatetags/approvals.py +19 -0
- 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 +5 -8
- nautobot/extras/tests/integration/test_configcontextschema.py +43 -48
- nautobot/extras/tests/integration/test_customfields.py +33 -33
- nautobot/extras/tests/integration/test_dynamicgroups.py +5 -10
- nautobot/extras/tests/integration/test_jobs.py +2 -4
- nautobot/extras/tests/integration/test_notes.py +3 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +35 -27
- nautobot/extras/tests/integration/test_relationships.py +7 -11
- nautobot/extras/tests/integration/test_tagfilter.py +3 -11
- nautobot/extras/tests/test_api.py +786 -242
- nautobot/extras/tests/test_approvals.py +715 -0
- nautobot/extras/tests/test_changelog.py +18 -14
- nautobot/extras/tests/test_customfields.py +14 -13
- nautobot/extras/tests/test_datasources.py +1 -1
- nautobot/extras/tests/test_dynamicgroups.py +9 -4
- nautobot/extras/tests/test_filters.py +443 -13
- nautobot/extras/tests/test_forms.py +18 -57
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +81 -1
- nautobot/extras/tests/test_models.py +378 -47
- nautobot/extras/tests/test_plugins.py +47 -13
- nautobot/extras/tests/test_relationships.py +7 -2
- nautobot/extras/tests/test_utils.py +2 -0
- nautobot/extras/tests/test_views.py +780 -493
- nautobot/extras/urls.py +36 -12
- nautobot/extras/utils.py +58 -12
- nautobot/extras/views.py +668 -209
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +35 -71
- 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 -1
- nautobot/ipam/querysets.py +1 -2
- nautobot/ipam/tables.py +26 -17
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +6 -6
- nautobot/ipam/templates/ipam/inc/service.html +8 -8
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +3 -2
- nautobot/ipam/templates/ipam/ipaddress.html +27 -13
- nautobot/ipam/templates/ipam/ipaddress_assign.html +31 -24
- nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +3 -3
- nautobot/ipam/templates/ipam/ipaddress_edit.html +9 -9
- nautobot/ipam/templates/ipam/ipaddress_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddress_merge.html +195 -186
- nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +7 -9
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +7 -5
- 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_create.html +9 -9
- nautobot/ipam/templates/ipam/prefix_list.html +15 -14
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/vlan.html +1 -1
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_update.html +6 -6
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/templates/ipam/vrf_edit.html +15 -15
- nautobot/ipam/tests/integration/test_prefixes.py +5 -13
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +20 -7
- nautobot/ipam/tests/test_filters.py +10 -0
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +24 -21
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +49 -7
- nautobot/project-static/dist/css/graphql-libraries.css +655 -0
- nautobot/project-static/dist/css/graphql-libraries.css.map +1 -0
- nautobot/project-static/dist/css/materialdesignicons.css +3 -0
- nautobot/project-static/dist/css/materialdesignicons.css.map +1 -0
- nautobot/project-static/dist/css/nautobot.css +13 -0
- nautobot/project-static/dist/css/nautobot.css.map +1 -0
- nautobot/project-static/dist/js/graphql-libraries.js +3 -0
- nautobot/project-static/dist/js/graphql-libraries.js.LICENSE.txt +62 -0
- nautobot/project-static/dist/js/graphql-libraries.js.map +1 -0
- nautobot/project-static/dist/js/libraries.js +3 -0
- nautobot/project-static/dist/js/libraries.js.LICENSE.txt +65 -0
- nautobot/project-static/dist/js/libraries.js.map +1 -0
- nautobot/project-static/dist/js/materialdesignicons.js +0 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js +2 -0
- nautobot/project-static/dist/js/nautobot-graphiql.js.map +1 -0
- nautobot/project-static/dist/js/nautobot.js +2 -0
- nautobot/project-static/dist/js/nautobot.js.map +1 -0
- nautobot/project-static/fonts/Montserrat-v30-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Light.woff2 +0 -0
- nautobot/project-static/fonts/Montserrat-v30-Regular.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Bold.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Light.woff2 +0 -0
- nautobot/project-static/fonts/Roboto-v48-Regular.woff2 +0 -0
- nautobot/project-static/img/jinja_logo.svg +21 -92
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/editor.js +4 -4
- nautobot/project-static/js/forms.js +67 -717
- nautobot/project-static/js/job_result.js +2 -2
- nautobot/project-static/nautobot-icons/360-degrees.svg +3 -0
- nautobot/project-static/nautobot-icons/arrow-decision.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-expand-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-2-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/arrows-move-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/atom.svg +3 -0
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/branch.svg +3 -0
- nautobot/project-static/nautobot-icons/briefcase-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data-2.svg +3 -0
- nautobot/project-static/nautobot-icons/cable-data.svg +3 -0
- nautobot/project-static/nautobot-icons/cast.svg +3 -0
- nautobot/project-static/nautobot-icons/check-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-circle.svg +3 -0
- nautobot/project-static/nautobot-icons/checkbox-rec.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-check.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud-upload.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +3 -0
- nautobot/project-static/nautobot-icons/compass.svg +3 -0
- nautobot/project-static/nautobot-icons/control-panel.svg +3 -0
- nautobot/project-static/nautobot-icons/credit-card.svg +3 -0
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +3 -0
- nautobot/project-static/nautobot-icons/direction.svg +3 -0
- nautobot/project-static/nautobot-icons/elements.svg +3 -0
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/globe-2.svg +3 -0
- nautobot/project-static/nautobot-icons/globe.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +3 -0
- nautobot/project-static/nautobot-icons/history.svg +3 -0
- nautobot/project-static/nautobot-icons/ip.svg +3 -0
- nautobot/project-static/nautobot-icons/laptop.svg +3 -0
- nautobot/project-static/nautobot-icons/lightning.svg +3 -0
- nautobot/project-static/nautobot-icons/list-unordered.svg +3 -0
- nautobot/project-static/nautobot-icons/map-view.svg +3 -0
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-2.svg +3 -0
- nautobot/project-static/nautobot-icons/pin-3.svg +3 -0
- nautobot/project-static/nautobot-icons/plug.svg +3 -0
- nautobot/project-static/nautobot-icons/refresh-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/rocket-2.svg +3 -0
- nautobot/project-static/nautobot-icons/rotate-cw.svg +3 -0
- nautobot/project-static/nautobot-icons/route.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +3 -0
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server-2.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +3 -0
- nautobot/project-static/nautobot-icons/share.svg +3 -0
- nautobot/project-static/nautobot-icons/shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/sitemap-outline.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert-2.svg +3 -0
- nautobot/project-static/nautobot-icons/sliders-vert.svg +3 -0
- nautobot/project-static/nautobot-icons/star-filled.svg +3 -0
- nautobot/project-static/nautobot-icons/star.svg +3 -0
- nautobot/project-static/nautobot-icons/transform.svg +3 -0
- nautobot/project-static/nautobot-icons/wifi.svg +3 -0
- 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/templates/tenancy/tenant_create.html +6 -6
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/.gitignore +137 -0
- nautobot/ui/.node-version +1 -0
- nautobot/ui/.prettierignore +3 -0
- nautobot/ui/eslint.config.js +33 -0
- nautobot/ui/package-lock.json +6594 -0
- nautobot/ui/package.json +67 -0
- nautobot/ui/prettier.config.js +9 -0
- nautobot/ui/src/js/collapse.js +69 -0
- nautobot/ui/src/js/cookie.js +31 -0
- nautobot/ui/src/js/draggable.js +101 -0
- nautobot/ui/src/js/drawer.js +106 -0
- nautobot/ui/src/js/form.js +23 -0
- nautobot/ui/src/js/history.js +51 -0
- nautobot/ui/src/js/nautobot-graphiql.js +19 -0
- nautobot/ui/src/js/nautobot.js +128 -0
- nautobot/ui/src/js/search.js +274 -0
- nautobot/ui/src/js/select2.js +318 -0
- nautobot/ui/src/js/sidenav.js +87 -0
- nautobot/ui/src/js/tabs.js +139 -0
- nautobot/ui/src/js/theme.js +104 -0
- nautobot/ui/src/js/utils.js +54 -0
- nautobot/ui/src/scss/colors.scss +58 -0
- nautobot/ui/src/scss/nautobot.scss +2471 -0
- nautobot/ui/webpack.config.js +148 -0
- nautobot/users/apps.py +3 -0
- nautobot/users/filters.py +7 -11
- nautobot/users/forms.py +10 -0
- nautobot/users/models.py +8 -0
- nautobot/users/templates/users/advanced_settings_edit.html +31 -21
- nautobot/users/templates/users/api_tokens.html +61 -51
- nautobot/users/templates/users/base.html +23 -31
- nautobot/users/templates/users/change_password.html +29 -19
- nautobot/users/templates/users/preferences.html +55 -45
- nautobot/users/templates/users/profile.html +45 -14
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/urls.py +2 -0
- nautobot/users/views.py +70 -2
- nautobot/virtualization/api/views.py +1 -1
- nautobot/virtualization/filters.py +18 -32
- nautobot/virtualization/forms.py +22 -59
- nautobot/virtualization/models.py +1 -19
- nautobot/virtualization/navigation.py +3 -1
- nautobot/virtualization/tables.py +10 -6
- nautobot/virtualization/templates/virtualization/cluster.html +13 -13
- nautobot/virtualization/templates/virtualization/cluster_edit.html +6 -6
- nautobot/virtualization/templates/virtualization/inc/virtualmachine_vminterface_filter.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_component_add.html +24 -16
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +4 -4
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +27 -25
- nautobot/virtualization/templates/virtualization/vminterface.html +5 -5
- nautobot/virtualization/templates/virtualization/vminterface_edit.html +27 -11
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_models.py +20 -5
- nautobot/virtualization/tests/test_views.py +3 -5
- nautobot/virtualization/urls.py +0 -11
- nautobot/virtualization/views.py +5 -122
- 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/filters.py +0 -8
- nautobot/wireless/navigation.py +3 -1
- nautobot/wireless/templates/wireless/wirelessnetwork_create.html +6 -6
- nautobot/wireless/tests/integration/test_radio_profile.py +3 -7
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/METADATA +5 -4
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/RECORD +802 -707
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/entry_points.txt +1 -0
- nautobot/core/management/commands/check_job_approval_status.py +0 -47
- nautobot/core/templates/search_form.html +0 -9
- nautobot/core/templates/utilities/templatetags/filter_form_modal.html +0 -87
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/extras/templates/extras/job_approval_request.html +0 -134
- nautobot/extras/templates/extras/scheduled_jobs_approval_queue_list.html +0 -28
- nautobot/ipam/mixins.py +0 -32
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css +0 -587
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +0 -6865
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +0 -1
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.eot +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.svg +0 -288
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.js +0 -2580
- nautobot/project-static/bootstrap-3.4.1-dist/js/bootstrap.min.js +0 -6
- nautobot/project-static/bootstrap-3.4.1-dist/js/npm.js +0 -13
- nautobot/project-static/clipboard.js-2.0.9/clipboard.min.js +0 -7
- nautobot/project-static/css/base.css +0 -1040
- nautobot/project-static/css/dark.css +0 -282
- nautobot/project-static/flatpickr-4.6.9/flatpickr.min.js +0 -2
- nautobot/project-static/flatpickr-4.6.9/themes/light.min.css +0 -1
- nautobot/project-static/graphiql-1.5.16/graphiql.min.css +0 -12
- nautobot/project-static/graphiql-1.5.16/graphiql.min.js +0 -11
- nautobot/project-static/highlight.js-11.9.0/github-dark.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/github.min.css +0 -10
- nautobot/project-static/highlight.js-11.9.0/highlight.min.js +0 -378
- nautobot/project-static/jquery/jquery-3.7.1.min.js +0 -2
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_444444_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_555555_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777620_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_777777_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_cc0000_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/images/ui-icons_ffffff_256x240.png +0 -0
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +0 -7
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +0 -6
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +0 -5
- nautobot/project-static/jquery-ui-1.13.2/jquery-ui.theme.min.css +0 -5
- nautobot/project-static/js/homepage_layout.js +0 -182
- nautobot/project-static/js/nav_menu.js +0 -250
- nautobot/project-static/js/theme.js +0 -133
- nautobot/project-static/materialdesignicons-7.4.47/LICENSE +0 -20
- nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +0 -3
- nautobot/project-static/react-16.14.0/react.production.min.js +0 -32
- nautobot/project-static/react-dom-16.14.0/react-dom.production.min.js +0 -239
- nautobot/project-static/select2-4.0.13/i18n/af.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ar.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/az.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bg.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bn.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/bs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ca.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/cs.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/da.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/de.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/dsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/el.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/en.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/es.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/et.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/eu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fa.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/fr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/gl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/he.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hsb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hu.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/hy.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/id.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/is.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/it.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ja.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ka.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/km.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ko.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/lv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/mk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ms.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nb.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ne.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/nl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ps.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt-BR.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/pt.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ro.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/ru.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sq.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr-Cyrl.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/sv.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/th.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/tr.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/uk.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/vi.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-CN.js +0 -3
- nautobot/project-static/select2-4.0.13/i18n/zh-TW.js +0 -3
- nautobot/project-static/select2-4.0.13/select2.min.css +0 -1
- nautobot/project-static/select2-4.0.13/select2.min.js +0 -2
- nautobot/project-static/select2-bootstrap-0.1.0-beta.10/select2-bootstrap.min.css +0 -7
- nautobot/project-static/subscriptions-transport-ws-0.9.18/client.min.js +0 -8
- nautobot/project-static/whatwg-fetch-3.6.2/fetch.umd.min.js +0 -8
- nautobot/virtualization/templates/virtualization/cluster_add_devices.html +0 -37
- /nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.ttf → dist/1fcc36272ea3e53d0031.ttf} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.eot → dist/2146c3c82b553977abc7.eot} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff → dist/e55a20c80650829ec5fd.woff} +0 -0
- /nautobot/project-static/{materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 → dist/ec024da790d2972da002.woff2} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/NOTICE +0 -0
- {nautobot-2.4.21.dist-info → nautobot-3.0.0a3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.contrib.auth.models import Group
|
|
5
|
+
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
6
|
+
from django.contrib.contenttypes.models import ContentType
|
|
7
|
+
from django.core.exceptions import ValidationError
|
|
8
|
+
from django.db import models
|
|
9
|
+
from django.utils import timezone
|
|
10
|
+
|
|
11
|
+
from nautobot.core.constants import CHARFIELD_MAX_LENGTH
|
|
12
|
+
from nautobot.core.models import BaseManager, BaseModel
|
|
13
|
+
from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
|
|
14
|
+
from nautobot.core.models.querysets import RestrictedQuerySet
|
|
15
|
+
from nautobot.extras.choices import ApprovalWorkflowStateChoices
|
|
16
|
+
from nautobot.extras.utils import extras_features, FeatureQuery
|
|
17
|
+
from nautobot.users.models import User
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ApprovalWorkflowDefinitionManager(BaseManager.from_queryset(RestrictedQuerySet)):
|
|
21
|
+
use_in_migrations = True
|
|
22
|
+
|
|
23
|
+
def find_for_model(self, model_instance: models.Model) -> Optional["ApprovalWorkflowDefinition"]:
|
|
24
|
+
"""Find the appropriate approval workflow definition for specific content-type.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
ApprovalWorkflowDefinition or None: The matching workflow definition or None if none found.
|
|
28
|
+
"""
|
|
29
|
+
content_type = ContentType.objects.get_for_model(model_instance)
|
|
30
|
+
|
|
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
|
+
|
|
34
|
+
for workflow_definition in workflow_definitions:
|
|
35
|
+
if not workflow_definition.model_constraints:
|
|
36
|
+
return workflow_definition
|
|
37
|
+
|
|
38
|
+
model_class = model_instance.__class__
|
|
39
|
+
try:
|
|
40
|
+
# Try to get the specific instance using the constraints
|
|
41
|
+
# NOTE: Any valid Django ORM lookup (e.g. __in, __icontains, __gte) will technically work here,
|
|
42
|
+
# since constraints are passed directly into .filter(). However, the current UI only supports
|
|
43
|
+
# simple key=value style constraints and does not provide validation for advanced lookups.
|
|
44
|
+
# Maybe in 3.1 replace with a FilterSet-based implementation (similar to Relationship.source_filter
|
|
45
|
+
# and Relationship.destination_filter) to provide full support
|
|
46
|
+
model_class.objects.filter(**workflow_definition.model_constraints).get(pk=model_instance.pk)
|
|
47
|
+
return workflow_definition
|
|
48
|
+
except model_class.DoesNotExist:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@extras_features(
|
|
55
|
+
"custom_links",
|
|
56
|
+
"custom_validators",
|
|
57
|
+
"export_templates",
|
|
58
|
+
"graphql",
|
|
59
|
+
"relationships",
|
|
60
|
+
"webhooks",
|
|
61
|
+
)
|
|
62
|
+
class ApprovalWorkflowDefinition(PrimaryModel):
|
|
63
|
+
"""ApprovalWorkflowDefinition model."""
|
|
64
|
+
|
|
65
|
+
name = models.CharField(
|
|
66
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
67
|
+
unique=True,
|
|
68
|
+
)
|
|
69
|
+
model_content_type = models.ForeignKey(
|
|
70
|
+
to=ContentType,
|
|
71
|
+
on_delete=models.PROTECT,
|
|
72
|
+
related_name="+",
|
|
73
|
+
limit_choices_to=FeatureQuery("approval_workflows"),
|
|
74
|
+
)
|
|
75
|
+
model_constraints = models.JSONField(
|
|
76
|
+
blank=True,
|
|
77
|
+
default=dict,
|
|
78
|
+
help_text="Constraints to filter the objects that can be approved using this workflow.",
|
|
79
|
+
)
|
|
80
|
+
weight = models.IntegerField(
|
|
81
|
+
default=0, help_text="Determines workflow relevance when multiple apply. Higher weight wins."
|
|
82
|
+
)
|
|
83
|
+
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
84
|
+
is_dynamic_group_associable = False
|
|
85
|
+
is_data_compliance_model = False
|
|
86
|
+
objects = ApprovalWorkflowDefinitionManager()
|
|
87
|
+
is_version_controlled = False
|
|
88
|
+
|
|
89
|
+
class Meta:
|
|
90
|
+
"""Meta class for ApprovalWorkflow Definition."""
|
|
91
|
+
|
|
92
|
+
verbose_name = "Approval Workflow Definition"
|
|
93
|
+
ordering = ["name"]
|
|
94
|
+
unique_together = [["model_content_type", "weight"]]
|
|
95
|
+
|
|
96
|
+
def __str__(self):
|
|
97
|
+
"""Stringify instance."""
|
|
98
|
+
return self.name
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@extras_features(
|
|
102
|
+
"custom_links",
|
|
103
|
+
"custom_validators",
|
|
104
|
+
"export_templates",
|
|
105
|
+
"graphql",
|
|
106
|
+
"webhooks",
|
|
107
|
+
)
|
|
108
|
+
class ApprovalWorkflowStageDefinition(OrganizationalModel):
|
|
109
|
+
"""ApprovalWorkflowStageDefinition model."""
|
|
110
|
+
|
|
111
|
+
approval_workflow_definition = models.ForeignKey(
|
|
112
|
+
to="extras.ApprovalWorkflowDefinition",
|
|
113
|
+
related_name="approval_workflow_stage_definitions",
|
|
114
|
+
verbose_name="Approval Workflow Definition",
|
|
115
|
+
on_delete=models.CASCADE,
|
|
116
|
+
help_text="Approval workflow definition to which this stage belongs.",
|
|
117
|
+
)
|
|
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.",
|
|
120
|
+
)
|
|
121
|
+
name = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
|
|
122
|
+
min_approvers = models.PositiveIntegerField(
|
|
123
|
+
verbose_name="Minimum approvers",
|
|
124
|
+
help_text="Minimum number of approvers required to approve this stage.",
|
|
125
|
+
)
|
|
126
|
+
denial_message = models.CharField(
|
|
127
|
+
max_length=CHARFIELD_MAX_LENGTH, blank=True, help_text="Message to show when the stage is denied."
|
|
128
|
+
)
|
|
129
|
+
approver_group = models.ForeignKey(
|
|
130
|
+
to=Group,
|
|
131
|
+
related_name="approval_workflow_stage_definitions",
|
|
132
|
+
verbose_name="Group",
|
|
133
|
+
help_text="Group of users who are eligible to approve this stage. Only admin users can create new groups.",
|
|
134
|
+
on_delete=models.PROTECT,
|
|
135
|
+
)
|
|
136
|
+
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
137
|
+
is_version_controlled = False
|
|
138
|
+
|
|
139
|
+
is_data_compliance_model = False
|
|
140
|
+
|
|
141
|
+
class Meta:
|
|
142
|
+
"""Meta class for ApprovalWorkflowStage."""
|
|
143
|
+
|
|
144
|
+
verbose_name = "Approval Workflow Stage Definition"
|
|
145
|
+
unique_together = [["approval_workflow_definition", "name"], ["approval_workflow_definition", "sequence"]]
|
|
146
|
+
ordering = ["approval_workflow_definition", "sequence"]
|
|
147
|
+
|
|
148
|
+
def __str__(self):
|
|
149
|
+
"""Stringify instance."""
|
|
150
|
+
return f"{self.approval_workflow_definition}: Stage {self.name}"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@extras_features(
|
|
154
|
+
"custom_links",
|
|
155
|
+
"custom_validators",
|
|
156
|
+
"export_templates",
|
|
157
|
+
"graphql",
|
|
158
|
+
"webhooks",
|
|
159
|
+
)
|
|
160
|
+
class ApprovalWorkflow(OrganizationalModel):
|
|
161
|
+
"""ApprovalWorkflow model."""
|
|
162
|
+
|
|
163
|
+
approval_workflow_definition = models.ForeignKey(
|
|
164
|
+
to="extras.ApprovalWorkflowDefinition",
|
|
165
|
+
related_name="approval_workflows",
|
|
166
|
+
verbose_name="Approval Workflow Definition",
|
|
167
|
+
on_delete=models.PROTECT,
|
|
168
|
+
help_text="Approval workflow definition to which this approval workflow belongs.",
|
|
169
|
+
)
|
|
170
|
+
object_under_review = GenericForeignKey(
|
|
171
|
+
ct_field="object_under_review_content_type", fk_field="object_under_review_object_id"
|
|
172
|
+
)
|
|
173
|
+
object_under_review_content_type = models.ForeignKey(
|
|
174
|
+
to=ContentType,
|
|
175
|
+
on_delete=models.PROTECT,
|
|
176
|
+
related_name="+",
|
|
177
|
+
limit_choices_to=FeatureQuery("approval_workflows"),
|
|
178
|
+
)
|
|
179
|
+
object_under_review_object_id = models.UUIDField(db_index=True)
|
|
180
|
+
current_state = models.CharField(
|
|
181
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
182
|
+
choices=ApprovalWorkflowStateChoices,
|
|
183
|
+
default=ApprovalWorkflowStateChoices.PENDING,
|
|
184
|
+
help_text="Current state of the approval workflow. Eligible values are: Pending, Approved, Denied.",
|
|
185
|
+
)
|
|
186
|
+
decision_date = models.DateTimeField(
|
|
187
|
+
blank=True,
|
|
188
|
+
null=True,
|
|
189
|
+
help_text="Date and time when the decision of approval/denial was made.",
|
|
190
|
+
)
|
|
191
|
+
# The user who triggered the approval workflow instance
|
|
192
|
+
user = models.ForeignKey(
|
|
193
|
+
to=settings.AUTH_USER_MODEL,
|
|
194
|
+
on_delete=models.SET_NULL,
|
|
195
|
+
related_name="approval_workflows",
|
|
196
|
+
blank=True,
|
|
197
|
+
null=True,
|
|
198
|
+
)
|
|
199
|
+
user_name = models.CharField(max_length=150, editable=False, db_index=True)
|
|
200
|
+
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
201
|
+
|
|
202
|
+
is_data_compliance_model = False
|
|
203
|
+
is_version_controlled = False
|
|
204
|
+
|
|
205
|
+
class Meta:
|
|
206
|
+
"""Meta class for ApprovalWorkflow."""
|
|
207
|
+
|
|
208
|
+
verbose_name = "Approval Workflow"
|
|
209
|
+
unique_together = [
|
|
210
|
+
"approval_workflow_definition",
|
|
211
|
+
"object_under_review_content_type",
|
|
212
|
+
"object_under_review_object_id",
|
|
213
|
+
]
|
|
214
|
+
ordering = ["approval_workflow_definition"]
|
|
215
|
+
|
|
216
|
+
def __str__(self):
|
|
217
|
+
"""Stringify instance."""
|
|
218
|
+
return f"{self.approval_workflow_definition.name}: {self.object_under_review} ({self.current_state})"
|
|
219
|
+
|
|
220
|
+
def get_current_state_class(self):
|
|
221
|
+
return ApprovalWorkflowStateChoices.CSS_CLASSES.get(self.current_state)
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def active_stage(self):
|
|
225
|
+
"""
|
|
226
|
+
Return the current stage of the workflow. The current stage is the first stage that is not yet approved.
|
|
227
|
+
If all stages are approved, return None.
|
|
228
|
+
Returns:
|
|
229
|
+
ApprovalWorkflowStage: The current stage of the workflow.
|
|
230
|
+
"""
|
|
231
|
+
first_nonapproved_stage = (
|
|
232
|
+
self.approval_workflow_stages.exclude(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
233
|
+
.order_by("approval_workflow_stage_definition__sequence")
|
|
234
|
+
.first()
|
|
235
|
+
)
|
|
236
|
+
return first_nonapproved_stage
|
|
237
|
+
|
|
238
|
+
def save(self, *args, **kwargs):
|
|
239
|
+
"""
|
|
240
|
+
Override the save() method to
|
|
241
|
+
1. Update its status to denied, if one of the approval workflow stage instances is denied.
|
|
242
|
+
2. Update its status to approved, if all of the approval workflow stage instances are approved.
|
|
243
|
+
Args:
|
|
244
|
+
*args: positional arguments
|
|
245
|
+
**kwargs: keyword arguments
|
|
246
|
+
"""
|
|
247
|
+
# Check of the object under review's content type matches the approval workflow definition's content type
|
|
248
|
+
if self.object_under_review_content_type != self.approval_workflow_definition.model_content_type:
|
|
249
|
+
raise ValidationError(
|
|
250
|
+
f"The content type {self.object_under_review_content_type} of "
|
|
251
|
+
f"the object under review does not match the content type {self.approval_workflow_definition.model_content_type} of the approval workflow definition."
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
# TODO need to check of the object fits the model_constraints of the approval workflow
|
|
255
|
+
if self.user:
|
|
256
|
+
self.user_name = self.user.username
|
|
257
|
+
# Check if there is one or more denied/canceled response for this stage instance
|
|
258
|
+
previous_state = self.current_state
|
|
259
|
+
denied_stages = self.approval_workflow_stages.filter(state=ApprovalWorkflowStateChoices.DENIED)
|
|
260
|
+
if denied_stages.exists():
|
|
261
|
+
self.current_state = ApprovalWorkflowStateChoices.DENIED
|
|
262
|
+
else:
|
|
263
|
+
canceled_stages = self.approval_workflow_stages.filter(state=ApprovalWorkflowStateChoices.CANCELED)
|
|
264
|
+
if canceled_stages.exists():
|
|
265
|
+
self.current_state = ApprovalWorkflowStateChoices.CANCELED
|
|
266
|
+
else:
|
|
267
|
+
# Check if all stages are approved
|
|
268
|
+
approved_stages = self.approval_workflow_stages.filter(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
269
|
+
approval_workflow_stage_count = (
|
|
270
|
+
self.approval_workflow_definition.approval_workflow_stage_definitions.count()
|
|
271
|
+
)
|
|
272
|
+
if approval_workflow_stage_count and approved_stages.count() == approval_workflow_stage_count:
|
|
273
|
+
self.current_state = ApprovalWorkflowStateChoices.APPROVED
|
|
274
|
+
|
|
275
|
+
# If the state is changed to APPROVED or DENIED or CANCELED from PENDING, set the decision date
|
|
276
|
+
if previous_state != self.current_state:
|
|
277
|
+
self.decision_date = timezone.now()
|
|
278
|
+
super().save(*args, **kwargs)
|
|
279
|
+
|
|
280
|
+
if previous_state != self.current_state:
|
|
281
|
+
if self.current_state == ApprovalWorkflowStateChoices.APPROVED:
|
|
282
|
+
self.object_under_review.on_workflow_approved(self)
|
|
283
|
+
elif self.current_state == ApprovalWorkflowStateChoices.DENIED:
|
|
284
|
+
self.object_under_review.on_workflow_denied(self)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@extras_features(
|
|
288
|
+
"custom_links",
|
|
289
|
+
"custom_validators",
|
|
290
|
+
"export_templates",
|
|
291
|
+
"graphql",
|
|
292
|
+
"webhooks",
|
|
293
|
+
)
|
|
294
|
+
class ApprovalWorkflowStage(OrganizationalModel):
|
|
295
|
+
"""ApprovalWorkflowStage model."""
|
|
296
|
+
|
|
297
|
+
approval_workflow = models.ForeignKey(
|
|
298
|
+
to="extras.ApprovalWorkflow",
|
|
299
|
+
related_name="approval_workflow_stages",
|
|
300
|
+
verbose_name="Approval Workflow",
|
|
301
|
+
on_delete=models.CASCADE,
|
|
302
|
+
help_text="Approval workflow to which this stage belongs.",
|
|
303
|
+
)
|
|
304
|
+
approval_workflow_stage_definition = models.ForeignKey(
|
|
305
|
+
to="extras.ApprovalWorkflowStageDefinition",
|
|
306
|
+
related_name="approval_workflow_stages",
|
|
307
|
+
verbose_name="Approval Workflow Stage Definition",
|
|
308
|
+
on_delete=models.CASCADE,
|
|
309
|
+
help_text="Approval workflow stage definition to which this stage belongs.",
|
|
310
|
+
)
|
|
311
|
+
state = models.CharField(
|
|
312
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
313
|
+
choices=ApprovalWorkflowStateChoices,
|
|
314
|
+
default=ApprovalWorkflowStateChoices.PENDING,
|
|
315
|
+
help_text="State of the approval workflow stage instance. Eligible values are: Pending, Approved, Denied.",
|
|
316
|
+
)
|
|
317
|
+
decision_date = models.DateTimeField(
|
|
318
|
+
blank=True,
|
|
319
|
+
null=True,
|
|
320
|
+
help_text="Date and time when the decision of approval/denial was made.",
|
|
321
|
+
)
|
|
322
|
+
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
323
|
+
is_version_controlled = False
|
|
324
|
+
|
|
325
|
+
is_data_compliance_model = False
|
|
326
|
+
|
|
327
|
+
class Meta:
|
|
328
|
+
"""Meta class for ApprovalWorkflowStage."""
|
|
329
|
+
|
|
330
|
+
verbose_name = "Approval Workflow Stage"
|
|
331
|
+
unique_together = [["approval_workflow", "approval_workflow_stage_definition"]]
|
|
332
|
+
ordering = ["approval_workflow", "approval_workflow_stage_definition__sequence"]
|
|
333
|
+
|
|
334
|
+
def __str__(self):
|
|
335
|
+
"""Stringify instance."""
|
|
336
|
+
return f"{self.approval_workflow_stage_definition}: {self.state}"
|
|
337
|
+
|
|
338
|
+
def get_state_class(self):
|
|
339
|
+
return ApprovalWorkflowStateChoices.CSS_CLASSES.get(self.state)
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def remaining_approvals(self):
|
|
343
|
+
"""
|
|
344
|
+
Calculate the number of remaining approvals needed for this stage instance to be approved.
|
|
345
|
+
Returns:
|
|
346
|
+
int: Number of remaining approvals needed.
|
|
347
|
+
"""
|
|
348
|
+
if self.approval_workflow.current_state != ApprovalWorkflowStateChoices.PENDING:
|
|
349
|
+
return 0
|
|
350
|
+
# Get the number of approvers who have already approved this stage instance
|
|
351
|
+
approved_responses = self.approval_workflow_stage_responses.filter(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
352
|
+
# Calculate the number of remaining approvals needed
|
|
353
|
+
return max(0, self.approval_workflow_stage_definition.min_approvers - approved_responses.count())
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def is_active_stage(self):
|
|
357
|
+
"""
|
|
358
|
+
Check if the stage is active.
|
|
359
|
+
An active stage is a stage that is not yet approved or denied.
|
|
360
|
+
Returns:
|
|
361
|
+
bool: True if the stage is active, False otherwise.
|
|
362
|
+
"""
|
|
363
|
+
active_stage = self.approval_workflow.active_stage
|
|
364
|
+
# If there is no active stage aka, all stages are approved, return False
|
|
365
|
+
if active_stage is None:
|
|
366
|
+
return False
|
|
367
|
+
# Check if the stage is the active stage and if the stage state is pending
|
|
368
|
+
return self.pk == active_stage.pk and active_stage.state == ApprovalWorkflowStateChoices.PENDING
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def users_that_already_approved(self):
|
|
372
|
+
"""
|
|
373
|
+
Get the users that have already approved this stage instance.
|
|
374
|
+
Returns:
|
|
375
|
+
list: List of users that have already approved this stage instance.
|
|
376
|
+
"""
|
|
377
|
+
return [
|
|
378
|
+
response.user
|
|
379
|
+
for response in self.approval_workflow_stage_responses.filter(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def users_that_already_denied(self):
|
|
384
|
+
"""
|
|
385
|
+
Get the users that have already denied this stage instance.
|
|
386
|
+
Returns:
|
|
387
|
+
list: List of users that have already denied this stage instance.
|
|
388
|
+
"""
|
|
389
|
+
return [
|
|
390
|
+
response.user
|
|
391
|
+
for response in self.approval_workflow_stage_responses.filter(state=ApprovalWorkflowStateChoices.DENIED)
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def should_render_state(self):
|
|
396
|
+
"""
|
|
397
|
+
Check if the stage state should be rendered in the UI.
|
|
398
|
+
The stage state should be rendered if the stage is approved or denied or it is currently active and pending.
|
|
399
|
+
Returns:
|
|
400
|
+
bool: True if the stage state should be rendered, False otherwise.
|
|
401
|
+
"""
|
|
402
|
+
if self.is_active_stage or self.state in [
|
|
403
|
+
ApprovalWorkflowStateChoices.APPROVED,
|
|
404
|
+
ApprovalWorkflowStateChoices.DENIED,
|
|
405
|
+
ApprovalWorkflowStateChoices.CANCELED,
|
|
406
|
+
]:
|
|
407
|
+
return True
|
|
408
|
+
return False
|
|
409
|
+
|
|
410
|
+
def save(self, *args, **kwargs):
|
|
411
|
+
"""
|
|
412
|
+
Override save method:
|
|
413
|
+
1. Set the stage as denied if one user denies it.
|
|
414
|
+
2. Set the stage as approved if min number of eligible users approve it.
|
|
415
|
+
3. Set decision_date when state is changed to APPROVED or DENIED if the state was just set to one of the terminal values.
|
|
416
|
+
4. Update the parent ApprovalWorkFlow instance status to denied, if the stage is denied.
|
|
417
|
+
2. Update the parent ApprovalWorkFlow instance status to approved, if all of the approval workflow stage instances are approved.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
*args: positional arguments
|
|
421
|
+
**kwargs: keyword arguments
|
|
422
|
+
"""
|
|
423
|
+
# store previous state in case it is changed
|
|
424
|
+
previous_state = self.state
|
|
425
|
+
# Check if the number of approvers is met
|
|
426
|
+
approved_responses = self.approval_workflow_stage_responses.filter(state=ApprovalWorkflowStateChoices.APPROVED)
|
|
427
|
+
if approved_responses.count() >= self.approval_workflow_stage_definition.min_approvers:
|
|
428
|
+
self.state = ApprovalWorkflowStateChoices.APPROVED
|
|
429
|
+
|
|
430
|
+
# Check if there is one or more denied/canceled response for this stage instance
|
|
431
|
+
# If so, set the stage as denied/canceled even tho previously the stage could be approved by enough number of approvers
|
|
432
|
+
denied_responses = self.approval_workflow_stage_responses.filter(state=ApprovalWorkflowStateChoices.DENIED)
|
|
433
|
+
if denied_responses.exists():
|
|
434
|
+
self.state = ApprovalWorkflowStateChoices.DENIED
|
|
435
|
+
else:
|
|
436
|
+
canceled_responses = self.approval_workflow_stage_responses.filter(
|
|
437
|
+
state=ApprovalWorkflowStateChoices.CANCELED
|
|
438
|
+
)
|
|
439
|
+
if canceled_responses.exists():
|
|
440
|
+
self.state = ApprovalWorkflowStateChoices.CANCELED
|
|
441
|
+
|
|
442
|
+
# Set the decision date if the state is changed to APPROVED or DENIED
|
|
443
|
+
decision_made = (
|
|
444
|
+
self.state
|
|
445
|
+
in [
|
|
446
|
+
ApprovalWorkflowStateChoices.APPROVED,
|
|
447
|
+
ApprovalWorkflowStateChoices.DENIED,
|
|
448
|
+
ApprovalWorkflowStateChoices.CANCELED,
|
|
449
|
+
]
|
|
450
|
+
and previous_state != self.state
|
|
451
|
+
)
|
|
452
|
+
if decision_made:
|
|
453
|
+
self.decision_date = timezone.now()
|
|
454
|
+
super().save(*args, **kwargs)
|
|
455
|
+
|
|
456
|
+
# Modify the parent ApprovalWorkflow to potentially update its state as well
|
|
457
|
+
# If this stage is approved or denied or canceled.
|
|
458
|
+
if decision_made:
|
|
459
|
+
approval_workflow = self.approval_workflow
|
|
460
|
+
# Check if there is one or more denied response for this stage instance
|
|
461
|
+
denied_or_canceled_stages = approval_workflow.approval_workflow_stages.filter(
|
|
462
|
+
state__in=[ApprovalWorkflowStateChoices.DENIED, ApprovalWorkflowStateChoices.CANCELED]
|
|
463
|
+
)
|
|
464
|
+
if (
|
|
465
|
+
denied_or_canceled_stages.exists()
|
|
466
|
+
and approval_workflow.current_state == ApprovalWorkflowStateChoices.PENDING
|
|
467
|
+
):
|
|
468
|
+
approval_workflow.save(using=kwargs.get("using"))
|
|
469
|
+
# Check if all stages are approved
|
|
470
|
+
approved_stages = approval_workflow.approval_workflow_stages.filter(
|
|
471
|
+
state=ApprovalWorkflowStateChoices.APPROVED
|
|
472
|
+
)
|
|
473
|
+
if (
|
|
474
|
+
approved_stages.count()
|
|
475
|
+
== approval_workflow.approval_workflow_definition.approval_workflow_stage_definitions.count()
|
|
476
|
+
and approval_workflow.current_state == ApprovalWorkflowStateChoices.PENDING
|
|
477
|
+
):
|
|
478
|
+
approval_workflow.save(using=kwargs.get("using"))
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@extras_features(
|
|
482
|
+
"custom_links",
|
|
483
|
+
"custom_validators",
|
|
484
|
+
"export_templates",
|
|
485
|
+
"graphql",
|
|
486
|
+
)
|
|
487
|
+
class ApprovalWorkflowStageResponse(BaseModel):
|
|
488
|
+
"""ApprovalWorkflowStageResponse model."""
|
|
489
|
+
|
|
490
|
+
approval_workflow_stage = models.ForeignKey(
|
|
491
|
+
to="extras.ApprovalWorkflowStage",
|
|
492
|
+
related_name="approval_workflow_stage_responses",
|
|
493
|
+
verbose_name="Approval Workflow Stage",
|
|
494
|
+
on_delete=models.CASCADE,
|
|
495
|
+
)
|
|
496
|
+
user = models.ForeignKey(
|
|
497
|
+
to=User,
|
|
498
|
+
related_name="approval_workflow_stage_responses",
|
|
499
|
+
verbose_name="User",
|
|
500
|
+
on_delete=models.CASCADE,
|
|
501
|
+
)
|
|
502
|
+
comments = models.CharField(
|
|
503
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
504
|
+
blank=True,
|
|
505
|
+
help_text="User comments to explain the decision that he/she made",
|
|
506
|
+
)
|
|
507
|
+
state = models.CharField(
|
|
508
|
+
max_length=CHARFIELD_MAX_LENGTH,
|
|
509
|
+
choices=ApprovalWorkflowStateChoices,
|
|
510
|
+
default=ApprovalWorkflowStateChoices.PENDING,
|
|
511
|
+
help_text="User response to this approval workflow stage instance. Eligible values are: Pending, Approved, Denied.",
|
|
512
|
+
)
|
|
513
|
+
documentation_static_path = "docs/user-guide/platform-functionality/approval-workflow.html"
|
|
514
|
+
is_version_controlled = False
|
|
515
|
+
|
|
516
|
+
is_data_compliance_model = False
|
|
517
|
+
|
|
518
|
+
class Meta:
|
|
519
|
+
"""Meta class for ApprovalWorkflowStageResponse."""
|
|
520
|
+
|
|
521
|
+
db_table = "extras_approvaluserresponse"
|
|
522
|
+
verbose_name = "Approval Workflow Stage Response"
|
|
523
|
+
ordering = ["approval_workflow_stage", "user"]
|
|
524
|
+
|
|
525
|
+
def __str__(self):
|
|
526
|
+
"""Stringify instance."""
|
|
527
|
+
return f"{self.approval_workflow_stage}: {self.user}"
|
|
528
|
+
|
|
529
|
+
def get_state_class(self):
|
|
530
|
+
return ApprovalWorkflowStateChoices.CSS_CLASSES.get(self.state)
|
|
531
|
+
|
|
532
|
+
def save(self, *args, **kwargs):
|
|
533
|
+
"""
|
|
534
|
+
Override the save method to call save() on the parent ApprovalWorkflowStage as well.
|
|
535
|
+
Args:
|
|
536
|
+
*args: positional arguments
|
|
537
|
+
**kwargs: keyword arguments
|
|
538
|
+
"""
|
|
539
|
+
super().save(*args, **kwargs)
|
|
540
|
+
# Call save() on the parent ApprovalWorkflowStage to potentially update its state as well
|
|
541
|
+
# If this stage is approved or denied and the approval workflow stage instance needs to be updated.
|
|
542
|
+
if self.state in [ApprovalWorkflowStateChoices.DENIED, ApprovalWorkflowStateChoices.CANCELED]:
|
|
543
|
+
# Check if the stage instance needs to be updated.
|
|
544
|
+
if self.approval_workflow_stage.state == ApprovalWorkflowStateChoices.PENDING:
|
|
545
|
+
self.approval_workflow_stage.save(using=kwargs.get("using"))
|
|
546
|
+
elif self.state == ApprovalWorkflowStateChoices.APPROVED:
|
|
547
|
+
approved_responses = self.approval_workflow_stage.approval_workflow_stage_responses.filter(
|
|
548
|
+
state=ApprovalWorkflowStateChoices.APPROVED
|
|
549
|
+
)
|
|
550
|
+
approved_response_count = approved_responses.count()
|
|
551
|
+
# Check if the number of approvers is met and the stage instance needs to be updated.
|
|
552
|
+
if (
|
|
553
|
+
approved_response_count == self.approval_workflow_stage.approval_workflow_stage_definition.min_approvers
|
|
554
|
+
and self.approval_workflow_stage.state == ApprovalWorkflowStateChoices.PENDING
|
|
555
|
+
):
|
|
556
|
+
self.approval_workflow_stage.save(using=kwargs.get("using"))
|
|
@@ -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 = (
|