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
nautobot/core/utils/lookup.py
CHANGED
|
@@ -1,17 +1,65 @@
|
|
|
1
1
|
"""Utilities for looking up related classes and information."""
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
from operator import attrgetter
|
|
4
5
|
import re
|
|
5
6
|
|
|
6
7
|
from django.apps import apps
|
|
7
8
|
from django.conf import settings
|
|
8
9
|
from django.contrib.auth.models import Group
|
|
9
10
|
from django.contrib.contenttypes.models import ContentType
|
|
10
|
-
from django.
|
|
11
|
+
from django.core.exceptions import ObjectDoesNotExist
|
|
12
|
+
from django.db.models import ForeignKey, Model
|
|
11
13
|
from django.urls import get_resolver, resolve, reverse, URLPattern, URLResolver
|
|
12
|
-
from django.utils.module_loading import import_string
|
|
13
14
|
from django.views.generic.base import RedirectView
|
|
14
15
|
|
|
16
|
+
from nautobot.core.utils.module_loading import import_string_optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def resolve_attr(obj, dotted_field):
|
|
20
|
+
"""
|
|
21
|
+
Resolve nested attributes on a Django model instance using a Django-style
|
|
22
|
+
double-underscore path (e.g. 'location_type__nestable').
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
obj: A Django model instance.
|
|
26
|
+
dotted_field (str): Dotted path to the attribute using '__' for
|
|
27
|
+
nested relationships or foreign keys.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
str | None:
|
|
31
|
+
- The string representation of the resolved attribute value.
|
|
32
|
+
- If the attribute is boolean, returns the field label in title case
|
|
33
|
+
if True, or 'Not <Field Label>' if False.
|
|
34
|
+
- Returns None if any attribute in the path does not exist or is None.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> resolve_attr(location, "name")
|
|
38
|
+
'Aisle'
|
|
39
|
+
|
|
40
|
+
>>> resolve_attr(location, "location_type__nestable")
|
|
41
|
+
'Nestable'
|
|
42
|
+
|
|
43
|
+
>>> resolve_attr(location, "location_type__nestable")
|
|
44
|
+
'Not Nestable'
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
val = attrgetter(dotted_field.replace("__", "."))(obj)
|
|
48
|
+
except (AttributeError, ObjectDoesNotExist):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# to avoid circular import
|
|
52
|
+
from nautobot.core.templatetags.helpers import bettertitle
|
|
53
|
+
|
|
54
|
+
# handle booleans specially
|
|
55
|
+
if isinstance(val, bool):
|
|
56
|
+
attrs = dotted_field.split("__")
|
|
57
|
+
# take last part of dotted_field ("nestable" in "location_type__nestable")
|
|
58
|
+
field_label = bettertitle(attrs[-1].replace("_", " "))
|
|
59
|
+
return field_label if val else f"Not {field_label}"
|
|
60
|
+
|
|
61
|
+
return str(val) if val else None
|
|
62
|
+
|
|
15
63
|
|
|
16
64
|
def get_changes_for_model(model):
|
|
17
65
|
"""
|
|
@@ -33,12 +81,14 @@ def get_changes_for_model(model):
|
|
|
33
81
|
def get_model_from_name(model_name):
|
|
34
82
|
"""Given a full model name in dotted format (example: `dcim.model`), a model class is returned if valid.
|
|
35
83
|
|
|
36
|
-
:
|
|
37
|
-
|
|
84
|
+
Args:
|
|
85
|
+
model_name (str): Full dotted name for a model as a string (ex: `dcim.model`)
|
|
38
86
|
|
|
39
|
-
:
|
|
87
|
+
Raises:
|
|
88
|
+
TypeError: If given model name is not found.
|
|
40
89
|
|
|
41
|
-
:
|
|
90
|
+
Returns:
|
|
91
|
+
(Model): Found model.
|
|
42
92
|
"""
|
|
43
93
|
|
|
44
94
|
try:
|
|
@@ -132,13 +182,7 @@ def get_related_class_for_model(model, module_name, object_suffix):
|
|
|
132
182
|
object_name = f"{model.__name__}{object_suffix}"
|
|
133
183
|
object_path = f"{app_config.name}.{module_name}.{object_name}"
|
|
134
184
|
|
|
135
|
-
|
|
136
|
-
return import_string(object_path)
|
|
137
|
-
# The name of the module is not correct or unable to find the desired object for this model
|
|
138
|
-
except (AttributeError, ImportError, ModuleNotFoundError):
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
return None
|
|
185
|
+
return import_string_optional(object_path)
|
|
142
186
|
|
|
143
187
|
|
|
144
188
|
def get_filterset_for_model(model):
|
|
@@ -329,6 +373,34 @@ def get_created_and_last_updated_usernames_for_model(instance):
|
|
|
329
373
|
return created_by, last_updated_by
|
|
330
374
|
|
|
331
375
|
|
|
376
|
+
def get_user_from_instance(instance):
|
|
377
|
+
"""
|
|
378
|
+
Retrieve the user object from a model instance, regardless of the field name.
|
|
379
|
+
|
|
380
|
+
This function inspects the given model instance to find a ForeignKey that points
|
|
381
|
+
to the current AUTH_USER_MODEL (as defined in Django settings). It does not require
|
|
382
|
+
the user-related field to have a fixed name (e.g., "user", "creator", "owner").
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
instance (models.Model): A Django model instance that may contain a ForeignKey
|
|
386
|
+
to the configured user model.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
User instance if a user-related ForeignKey exists and is populated,
|
|
390
|
+
otherwise None.
|
|
391
|
+
|
|
392
|
+
Example:
|
|
393
|
+
>>> schedule = ScheduledJob.objects.first()
|
|
394
|
+
>>> user = get_user_from_instance(schedule)
|
|
395
|
+
>>> print(user.username)
|
|
396
|
+
"""
|
|
397
|
+
UserModel = settings.AUTH_USER_MODEL
|
|
398
|
+
for field in instance._meta.get_fields():
|
|
399
|
+
if isinstance(field, ForeignKey) and field.related_model._meta.label == UserModel:
|
|
400
|
+
return getattr(instance, field.name, None)
|
|
401
|
+
return None
|
|
402
|
+
|
|
403
|
+
|
|
332
404
|
def get_url_patterns(urlconf=None, patterns_list=None, base_path="/", ignore_redirects=False):
|
|
333
405
|
"""
|
|
334
406
|
Recursively yield a list of registered URL patterns.
|
|
@@ -427,6 +499,8 @@ def get_url_for_url_pattern(url_pattern):
|
|
|
427
499
|
# Fixup tokens in path-style "classic" view URLs:
|
|
428
500
|
# "/admin/users/user/<id>/password/"
|
|
429
501
|
url = re.sub(r"<id>", "00000000-0000-0000-0000-000000000000", url)
|
|
502
|
+
# "/data-validation-engine/data-compliance/<model>/00000000-0000-0000-0000-000000000000/"
|
|
503
|
+
url = re.sub(r"<model>", "circuits.circuit", url)
|
|
430
504
|
# "/silk/request/<uuid:request_id>/profile/<int:profile_id>/"
|
|
431
505
|
url = re.sub(r"<int:\w+>", "1", url)
|
|
432
506
|
# "/admin/admin/logentry/<path:object_id>/"
|
|
@@ -71,10 +71,12 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
71
71
|
|
|
72
72
|
This will replace the old content type with the new content type on the following models:
|
|
73
73
|
- ComputedField.content_type
|
|
74
|
+
- ContactAssociation.associated_object_type
|
|
74
75
|
- CustomLink.content_type
|
|
75
76
|
- ExportTemplate.content_type
|
|
76
77
|
- Note.assigned_object_type
|
|
77
78
|
- ObjectChange.changed_object_type
|
|
79
|
+
- ObjectMetadata.assigned_object_type
|
|
78
80
|
- Relationship.source_type
|
|
79
81
|
- Relationship.destination_type
|
|
80
82
|
- RelationshipAssociation.source_type
|
|
@@ -86,7 +88,9 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
86
88
|
- CustomField.content_types
|
|
87
89
|
- JobButton.content_types
|
|
88
90
|
- JobHook.content_types
|
|
91
|
+
- MetadataType.content_types
|
|
89
92
|
- ObjectPermission.object_types
|
|
93
|
+
- Role.content_types
|
|
90
94
|
- Status.content_types
|
|
91
95
|
- Tag.content_types
|
|
92
96
|
- WebHook.content_types
|
|
@@ -101,16 +105,20 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
101
105
|
|
|
102
106
|
"""
|
|
103
107
|
ComputedField = apps.get_model("extras", "ComputedField")
|
|
108
|
+
ContactAssociation = apps.get_model("extras", "ContactAssociation")
|
|
104
109
|
CustomField = apps.get_model("extras", "CustomField")
|
|
105
110
|
CustomLink = apps.get_model("extras", "CustomLink")
|
|
106
111
|
ExportTemplate = apps.get_model("extras", "ExportTemplate")
|
|
107
112
|
JobButton = apps.get_model("extras", "JobButton")
|
|
108
113
|
JobHook = apps.get_model("extras", "JobHook")
|
|
114
|
+
MetadataType = apps.get_model("extras", "MetadataType")
|
|
109
115
|
Note = apps.get_model("extras", "Note")
|
|
110
116
|
ObjectChange = apps.get_model("extras", "ObjectChange")
|
|
117
|
+
ObjectMetadata = apps.get_model("extras", "ObjectMetadata")
|
|
111
118
|
ObjectPermission = apps.get_model("users", "ObjectPermission")
|
|
112
119
|
Relationship = apps.get_model("extras", "Relationship")
|
|
113
120
|
RelationshipAssociation = apps.get_model("extras", "RelationshipAssociation")
|
|
121
|
+
Role = apps.get_model("extras", "Role")
|
|
114
122
|
Status = apps.get_model("extras", "Status")
|
|
115
123
|
Tag = apps.get_model("extras", "Tag")
|
|
116
124
|
TaggedItem = apps.get_model("extras", "TaggedItem")
|
|
@@ -119,6 +127,9 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
119
127
|
# Migrate ComputedField content type
|
|
120
128
|
ComputedField.objects.filter(content_type=old_ct).update(content_type=new_ct)
|
|
121
129
|
|
|
130
|
+
# Migrate ContactAssociation content type
|
|
131
|
+
ContactAssociation.objects.filter(associated_object_type=old_ct).update(associated_object_type=new_ct)
|
|
132
|
+
|
|
122
133
|
# Migrate CustomField content type
|
|
123
134
|
for cf in CustomField.objects.filter(content_types=old_ct):
|
|
124
135
|
cf.content_types.add(new_ct)
|
|
@@ -137,12 +148,19 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
137
148
|
for job_hook in JobHook.objects.filter(content_types=old_ct):
|
|
138
149
|
job_hook.content_types.add(new_ct)
|
|
139
150
|
|
|
151
|
+
# Migrate MetadataType content type
|
|
152
|
+
for metadata_type in MetadataType.objects.filter(content_types=old_ct):
|
|
153
|
+
metadata_type.content_types.add(new_ct)
|
|
154
|
+
|
|
140
155
|
# Migrate Note content type
|
|
141
156
|
Note.objects.filter(assigned_object_type=old_ct).update(assigned_object_type=new_ct)
|
|
142
157
|
|
|
143
158
|
# Migrate ObjectChange content type
|
|
144
159
|
ObjectChange.objects.filter(changed_object_type=old_ct).update(changed_object_type=new_ct)
|
|
145
160
|
|
|
161
|
+
# Migrate ObjectMetadata content type
|
|
162
|
+
ObjectMetadata.objects.filter(assigned_object_type=old_ct).update(assigned_object_type=new_ct)
|
|
163
|
+
|
|
146
164
|
# Migrate ObjectPermission content type
|
|
147
165
|
for object_permission in ObjectPermission.objects.filter(object_types=old_ct):
|
|
148
166
|
object_permission.object_types.add(new_ct)
|
|
@@ -155,6 +173,10 @@ def migrate_content_type_references_to_new_model(apps, old_ct, new_ct):
|
|
|
155
173
|
RelationshipAssociation.objects.filter(source_type=old_ct).update(source_type=new_ct)
|
|
156
174
|
RelationshipAssociation.objects.filter(destination_type=old_ct).update(destination_type=new_ct)
|
|
157
175
|
|
|
176
|
+
# Migrate Role content type
|
|
177
|
+
for role in Role.objects.filter(content_types=old_ct):
|
|
178
|
+
role.content_types.add(new_ct)
|
|
179
|
+
|
|
158
180
|
# Migrate Status content type
|
|
159
181
|
for status in Status.objects.filter(content_types=old_ct):
|
|
160
182
|
status.content_types.add(new_ct)
|
|
@@ -7,9 +7,30 @@ import os
|
|
|
7
7
|
import pkgutil
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
+
from django.utils.module_loading import import_string
|
|
11
|
+
|
|
10
12
|
logger = logging.getLogger(__name__)
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def import_string_optional(dotted_path):
|
|
16
|
+
"""An extension/wrapper of Django's `import_string()` that returns `None` if no such dotted path exists."""
|
|
17
|
+
try:
|
|
18
|
+
return import_string(dotted_path)
|
|
19
|
+
except ModuleNotFoundError as err:
|
|
20
|
+
# No such module
|
|
21
|
+
module_name, _ = dotted_path.rsplit(".", 1)
|
|
22
|
+
if module_name.startswith(err.name): # tried to import foo.bar.baz but couldn't find foo.bar, etc.
|
|
23
|
+
return None
|
|
24
|
+
# Some import *from within* the given module couldn't find what it was looking for?
|
|
25
|
+
raise
|
|
26
|
+
except ImportError as err:
|
|
27
|
+
if "does not define" in str(err):
|
|
28
|
+
# Exception raised by Django if the module exists but has no such attribute
|
|
29
|
+
return None
|
|
30
|
+
# Maybe a legitimate problem with the import?
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
|
|
13
34
|
@contextmanager
|
|
14
35
|
def _temporarily_add_to_sys_path(path):
|
|
15
36
|
"""
|
|
@@ -77,6 +98,8 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
|
77
98
|
module_prefix = None
|
|
78
99
|
else:
|
|
79
100
|
module_prefix = ".".join(module_path)
|
|
101
|
+
|
|
102
|
+
loaded_modules = []
|
|
80
103
|
with _temporarily_add_to_sys_path(path):
|
|
81
104
|
for finder, discovered_module_name, is_package in pkgutil.walk_packages([path], onerror=logger.error):
|
|
82
105
|
if module_prefix and not (
|
|
@@ -114,7 +137,10 @@ def import_modules_privately(path, module_path=None, ignore_import_errors=True):
|
|
|
114
137
|
else:
|
|
115
138
|
module = importlib.import_module(discovered_module_name)
|
|
116
139
|
importlib.reload(module)
|
|
140
|
+
loaded_modules.append(module)
|
|
117
141
|
except Exception as exc:
|
|
118
142
|
logger.error("Unable to load module %s from %s: %s", discovered_module_name, path, exc)
|
|
119
143
|
if not ignore_import_errors:
|
|
120
144
|
raise
|
|
145
|
+
|
|
146
|
+
return loaded_modules
|
|
@@ -7,8 +7,9 @@ def get_permission_for_model(model, action):
|
|
|
7
7
|
"""
|
|
8
8
|
Resolve the named permission for a given model (or instance) and action (e.g. view or add).
|
|
9
9
|
|
|
10
|
-
:
|
|
11
|
-
|
|
10
|
+
Args:
|
|
11
|
+
model (Any): A model or instance
|
|
12
|
+
action (str): View, add, change, or delete
|
|
12
13
|
"""
|
|
13
14
|
if action not in ("view", "add", "change", "delete"):
|
|
14
15
|
raise ValueError(f"Unsupported action: {action}")
|
|
@@ -21,7 +22,8 @@ def resolve_permission(name):
|
|
|
21
22
|
Given a permission name, return the app_label, action, and model_name components. For example, "dcim.view_location"
|
|
22
23
|
returns ("dcim", "view", "location").
|
|
23
24
|
|
|
24
|
-
:
|
|
25
|
+
Args:
|
|
26
|
+
name (str): Permission name in the format `<app_label>.<action>_<model>`
|
|
25
27
|
"""
|
|
26
28
|
try:
|
|
27
29
|
app_label, codename = name.split(".")
|
|
@@ -37,7 +39,8 @@ def resolve_permission_ct(name):
|
|
|
37
39
|
Given a permission name, return the relevant ContentType and action. For example, "dcim.view_location" returns
|
|
38
40
|
(Location, "view").
|
|
39
41
|
|
|
40
|
-
:
|
|
42
|
+
Args:
|
|
43
|
+
name (str): Permission name in the format `<app_label>.<action>_<model>`
|
|
41
44
|
"""
|
|
42
45
|
app_label, action, model_name = resolve_permission(name)
|
|
43
46
|
try:
|
|
@@ -52,7 +55,8 @@ def permission_is_exempt(name):
|
|
|
52
55
|
"""
|
|
53
56
|
Determine whether a specified permission is exempt from evaluation.
|
|
54
57
|
|
|
55
|
-
:
|
|
58
|
+
Args:
|
|
59
|
+
name (str): Permission name in the format `<app_label>.<action>_<model>`
|
|
56
60
|
"""
|
|
57
61
|
app_label, action, model_name = resolve_permission(name)
|
|
58
62
|
|
nautobot/core/views/__init__.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
import datetime
|
|
3
|
+
from importlib import resources
|
|
3
4
|
import logging
|
|
5
|
+
import mimetypes
|
|
4
6
|
import os
|
|
5
7
|
import platform
|
|
6
8
|
import posixpath
|
|
@@ -16,7 +18,7 @@ from django.contrib.auth.decorators import permission_required
|
|
|
16
18
|
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
|
|
17
19
|
from django.contrib.contenttypes.models import ContentType
|
|
18
20
|
from django.core.cache import cache
|
|
19
|
-
from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
21
|
+
from django.http import FileResponse, HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
20
22
|
from django.shortcuts import get_object_or_404, render
|
|
21
23
|
from django.template import loader, RequestContext, Template
|
|
22
24
|
from django.template.exceptions import TemplateDoesNotExist
|
|
@@ -47,11 +49,13 @@ from rest_framework.views import APIView
|
|
|
47
49
|
|
|
48
50
|
from nautobot.core.celery import app
|
|
49
51
|
from nautobot.core.constants import SEARCH_MAX_RESULTS
|
|
50
|
-
from nautobot.core.forms import SearchForm
|
|
51
52
|
from nautobot.core.releases import get_latest_release
|
|
53
|
+
from nautobot.core.ui.breadcrumbs import Breadcrumbs, ViewNameBreadcrumbItem
|
|
54
|
+
from nautobot.core.ui.titles import Titles
|
|
52
55
|
from nautobot.core.utils.config import get_settings_or_config
|
|
53
56
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
54
57
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
58
|
+
from nautobot.core.views.mixins import UIComponentsMixin
|
|
55
59
|
from nautobot.core.views.utils import (
|
|
56
60
|
generate_latest_with_cache,
|
|
57
61
|
is_metrics_experimental_caching_enabled,
|
|
@@ -59,7 +63,9 @@ from nautobot.core.views.utils import (
|
|
|
59
63
|
)
|
|
60
64
|
from nautobot.extras.forms import GraphQLQueryForm
|
|
61
65
|
from nautobot.extras.models import FileProxy, GraphQLQuery, Status
|
|
66
|
+
from nautobot.extras.plugins.urls import BASE_URL_TO_APP_LABEL
|
|
62
67
|
from nautobot.extras.registry import registry
|
|
68
|
+
from nautobot.extras.tables import StatusTable
|
|
63
69
|
|
|
64
70
|
logger = logging.getLogger(__name__)
|
|
65
71
|
|
|
@@ -107,7 +113,7 @@ class HomeView(AccessMixin, TemplateView):
|
|
|
107
113
|
context = self.get_context_data()
|
|
108
114
|
context.update(
|
|
109
115
|
{
|
|
110
|
-
"
|
|
116
|
+
"title": "Home",
|
|
111
117
|
"new_release": new_release,
|
|
112
118
|
}
|
|
113
119
|
)
|
|
@@ -141,6 +147,40 @@ class HomeView(AccessMixin, TemplateView):
|
|
|
141
147
|
return self.render_to_response(context)
|
|
142
148
|
|
|
143
149
|
|
|
150
|
+
class AppDocsView(LoginRequiredMixin, View):
|
|
151
|
+
"""
|
|
152
|
+
Serve documentation files for any pip-installed app from inside the package,
|
|
153
|
+
only for authenticated users.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def get(self, request, app_base_url, path="index.html"):
|
|
157
|
+
app_label = BASE_URL_TO_APP_LABEL.get(app_base_url)
|
|
158
|
+
if not app_label:
|
|
159
|
+
return JsonResponse({"detail": f"Unknown base_url '{app_base_url}'."}, status=404)
|
|
160
|
+
try:
|
|
161
|
+
base_dir = resources.files(app_label)
|
|
162
|
+
except ModuleNotFoundError:
|
|
163
|
+
return JsonResponse({"detail": f"App {app_label} not found."}, status=404)
|
|
164
|
+
|
|
165
|
+
# Dir to documentation inside the package
|
|
166
|
+
docs_dir = base_dir / "docs"
|
|
167
|
+
# Normalize path to avoid (../) etc.
|
|
168
|
+
normalized_path = posixpath.normpath(path).lstrip("/")
|
|
169
|
+
file_path = docs_dir / normalized_path
|
|
170
|
+
|
|
171
|
+
# Additional check to ensure the resolved path is still within docs_dir
|
|
172
|
+
if not file_path.resolve().is_relative_to(docs_dir.resolve()):
|
|
173
|
+
return JsonResponse({"detail": "Access denied."}, status=403)
|
|
174
|
+
|
|
175
|
+
if not file_path.is_file():
|
|
176
|
+
return JsonResponse({"detail": f"File {file_path} not found."}, status=404)
|
|
177
|
+
|
|
178
|
+
# Determine the MIME type based on the file extension and return the file as an HTTP response.
|
|
179
|
+
# This ensures that browsers interpret the file correctly (e.g., HTML, CSS, JS, images).
|
|
180
|
+
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
181
|
+
return FileResponse(open(file_path, "rb"), content_type=content_type)
|
|
182
|
+
|
|
183
|
+
|
|
144
184
|
class MediaView(AccessMixin, View):
|
|
145
185
|
"""
|
|
146
186
|
Serves media files while enforcing login restrictions.
|
|
@@ -160,8 +200,9 @@ class MediaView(AccessMixin, View):
|
|
|
160
200
|
return self.handle_no_permission()
|
|
161
201
|
|
|
162
202
|
|
|
163
|
-
class WorkerStatusView(UserPassesTestMixin, TemplateView):
|
|
203
|
+
class WorkerStatusView(UserPassesTestMixin, UIComponentsMixin, TemplateView):
|
|
164
204
|
template_name = "utilities/worker_status.html"
|
|
205
|
+
view_titles = Titles(titles={"*": "Nautobot Worker Status"})
|
|
165
206
|
|
|
166
207
|
def test_func(self):
|
|
167
208
|
return self.request.user.is_staff
|
|
@@ -272,13 +313,24 @@ class WorkerStatusView(UserPassesTestMixin, TemplateView):
|
|
|
272
313
|
"timeout": timeout,
|
|
273
314
|
"workers": workers,
|
|
274
315
|
},
|
|
316
|
+
"breadcrumbs": self.get_breadcrumbs(),
|
|
317
|
+
"view_titles": self.get_view_titles(),
|
|
275
318
|
}
|
|
276
319
|
|
|
277
320
|
return self.render_to_response(context)
|
|
278
321
|
|
|
279
322
|
|
|
280
|
-
class ThemePreviewView(LoginRequiredMixin, TemplateView):
|
|
323
|
+
class ThemePreviewView(LoginRequiredMixin, UIComponentsMixin, TemplateView):
|
|
281
324
|
template_name = "utilities/theme_preview.html"
|
|
325
|
+
view_titles = Titles(titles={"*": "Nautobot Theme Preview"})
|
|
326
|
+
breadcrumbs = Breadcrumbs(
|
|
327
|
+
items={
|
|
328
|
+
"generic": [
|
|
329
|
+
ViewNameBreadcrumbItem(view_name="home", label="Nautobot"),
|
|
330
|
+
ViewNameBreadcrumbItem(view_name="theme_preview", label="Theme Preview"),
|
|
331
|
+
],
|
|
332
|
+
},
|
|
333
|
+
)
|
|
282
334
|
|
|
283
335
|
def get_context_data(self, **kwargs):
|
|
284
336
|
return {
|
|
@@ -286,6 +338,10 @@ class ThemePreviewView(LoginRequiredMixin, TemplateView):
|
|
|
286
338
|
"object": Status.objects.first(),
|
|
287
339
|
"verbose_name": Status.objects.all().model._meta.verbose_name,
|
|
288
340
|
"verbose_name_plural": Status.objects.all().model._meta.verbose_name_plural,
|
|
341
|
+
"table": StatusTable(Status.objects.all()[:3]),
|
|
342
|
+
"view_titles": self.get_view_titles(),
|
|
343
|
+
"breadcrumbs": self.get_breadcrumbs(),
|
|
344
|
+
"view_action": "generic",
|
|
289
345
|
}
|
|
290
346
|
|
|
291
347
|
|
|
@@ -298,70 +354,57 @@ class SearchView(AccessMixin, View):
|
|
|
298
354
|
|
|
299
355
|
# No query
|
|
300
356
|
if "q" not in request.GET:
|
|
301
|
-
return render(
|
|
302
|
-
request,
|
|
303
|
-
"search.html",
|
|
304
|
-
{
|
|
305
|
-
"form": SearchForm(),
|
|
306
|
-
},
|
|
307
|
-
)
|
|
357
|
+
return render(request, "search.html", {})
|
|
308
358
|
|
|
309
|
-
form = SearchForm(request.GET)
|
|
310
359
|
results = []
|
|
311
360
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
"url": f"{reverse(url)}?q={form.cleaned_data.get('q')}",
|
|
355
|
-
}
|
|
356
|
-
)
|
|
357
|
-
except NoReverseMatch:
|
|
358
|
-
messages.error(request, f'Missing URL "{url}" - unable to show search results for {modelname}.')
|
|
361
|
+
# Build the list of (app_label, modelname) tuples, representing all models included in the global search,
|
|
362
|
+
# based on the `app_config.searchable_models` list (if any) defined by each app
|
|
363
|
+
searchable_models = []
|
|
364
|
+
for app_config in apps.get_app_configs():
|
|
365
|
+
if hasattr(app_config, "searchable_models"):
|
|
366
|
+
searchable_models += [(app_config.label, modelname) for modelname in app_config.searchable_models]
|
|
367
|
+
|
|
368
|
+
# Searching all object types
|
|
369
|
+
obj_types = [model_info[1] for model_info in searchable_models]
|
|
370
|
+
|
|
371
|
+
for label, modelname in searchable_models:
|
|
372
|
+
if modelname not in obj_types:
|
|
373
|
+
continue
|
|
374
|
+
# Based on the label and modelname, reverse-lookup the list URL, then the view or UIViewSet
|
|
375
|
+
# corresponding to that URL, and finally the queryset, filterset, and table classes needed
|
|
376
|
+
# to find and display the model search results.
|
|
377
|
+
url = get_route_for_model(f"{label}.{modelname}", "list")
|
|
378
|
+
try:
|
|
379
|
+
view_func = resolve(reverse(url)).func
|
|
380
|
+
# For UIViewSet, view_func.cls gets what we need; for an ObjectListView, view_func.view_class is it.
|
|
381
|
+
view_or_viewset = getattr(view_func, "cls", getattr(view_func, "view_class", None))
|
|
382
|
+
queryset = view_or_viewset.queryset.restrict(request.user, "view")
|
|
383
|
+
# For a UIViewSet, .filterset_class, for an ObjectListView, .filterset.
|
|
384
|
+
filterset = getattr(view_or_viewset, "filterset_class", getattr(view_or_viewset, "filterset", None))
|
|
385
|
+
# For a UIViewSet, .table_class, for an ObjectListView, .table.
|
|
386
|
+
table = getattr(view_or_viewset, "table_class", getattr(view_or_viewset, "table", None))
|
|
387
|
+
|
|
388
|
+
# Construct the results table for this object type
|
|
389
|
+
filtered_queryset = filterset({"q": request.GET.get("q")}, queryset=queryset).qs
|
|
390
|
+
table = table(filtered_queryset, hide_hierarchy_ui=True, orderable=False)
|
|
391
|
+
table.paginate(per_page=SEARCH_MAX_RESULTS)
|
|
392
|
+
|
|
393
|
+
if table.page:
|
|
394
|
+
results.append(
|
|
395
|
+
{
|
|
396
|
+
"name": queryset.model._meta.verbose_name_plural,
|
|
397
|
+
"table": table,
|
|
398
|
+
"url": f"{reverse(url)}?q={request.GET.get('q')}",
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
except NoReverseMatch:
|
|
402
|
+
messages.error(request, f'Missing URL "{url}" - unable to show search results for {modelname}.')
|
|
359
403
|
|
|
360
404
|
return render(
|
|
361
405
|
request,
|
|
362
406
|
"search.html",
|
|
363
407
|
{
|
|
364
|
-
"form": form,
|
|
365
408
|
"results": results,
|
|
366
409
|
},
|
|
367
410
|
)
|
|
@@ -422,7 +465,9 @@ def csrf_failure(request, reason="", template_name="403_csrf_failure.html"):
|
|
|
422
465
|
return HttpResponseForbidden(t.render(context), content_type="text/html")
|
|
423
466
|
|
|
424
467
|
|
|
425
|
-
class CustomGraphQLView(LoginRequiredMixin, GraphQLView):
|
|
468
|
+
class CustomGraphQLView(LoginRequiredMixin, UIComponentsMixin, GraphQLView):
|
|
469
|
+
view_titles = Titles(titles={"*": "GraphiQL"})
|
|
470
|
+
|
|
426
471
|
def render_graphiql(self, request, **data):
|
|
427
472
|
query_name = request.GET.get("name")
|
|
428
473
|
if query_name:
|
|
@@ -430,6 +475,8 @@ class CustomGraphQLView(LoginRequiredMixin, GraphQLView):
|
|
|
430
475
|
data["editing"] = True
|
|
431
476
|
data["saved_graphiql_queries"] = GraphQLQuery.objects.all()
|
|
432
477
|
data["form"] = GraphQLQueryForm
|
|
478
|
+
data["breadcrumbs"] = self.get_breadcrumbs()
|
|
479
|
+
data["view_titles"] = self.get_view_titles()
|
|
433
480
|
return render(request, self.graphiql_template, data)
|
|
434
481
|
|
|
435
482
|
|
|
@@ -524,13 +571,14 @@ def get_file_with_authorization(request, *args, **kwargs):
|
|
|
524
571
|
return get_file(request, *args, **kwargs)
|
|
525
572
|
|
|
526
573
|
|
|
527
|
-
class AboutView(AccessMixin, TemplateView):
|
|
574
|
+
class AboutView(AccessMixin, UIComponentsMixin, TemplateView):
|
|
528
575
|
"""
|
|
529
576
|
Nautobot About View which displays general information about Nautobot and contact details
|
|
530
577
|
for Network to Code.
|
|
531
578
|
"""
|
|
532
579
|
|
|
533
580
|
template_name = "about.html"
|
|
581
|
+
view_titles = Titles(titles={"*": "About Nautobot"})
|
|
534
582
|
|
|
535
583
|
def get(self, request, *args, **kwargs):
|
|
536
584
|
# Redirect user to login page if not authenticated
|
|
@@ -558,6 +606,8 @@ class AboutView(AccessMixin, TemplateView):
|
|
|
558
606
|
"new_release": new_release,
|
|
559
607
|
"support_contract_active": support_contract_active,
|
|
560
608
|
"support_expiration_date": support_expiration_date,
|
|
609
|
+
"view_titles": self.get_view_titles(),
|
|
610
|
+
"breadcrumbs": self.get_breadcrumbs(),
|
|
561
611
|
}
|
|
562
612
|
)
|
|
563
613
|
|
|
@@ -568,3 +618,4 @@ class RenderJinjaView(LoginRequiredMixin, TemplateView):
|
|
|
568
618
|
"""Render a Jinja template with context data."""
|
|
569
619
|
|
|
570
620
|
template_name = "utilities/render_jinja2.html"
|
|
621
|
+
extra_context = {"view_titles": Titles(titles={"*": "Jinja Template Renderer"})}
|