nautobot 3.0.0a2__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/choices.py +4 -2
- nautobot/apps/filters.py +7 -9
- nautobot/apps/models.py +2 -2
- nautobot/apps/ui.py +13 -1
- nautobot/apps/utils.py +8 -0
- nautobot/circuits/filters.py +3 -2
- nautobot/circuits/navigation.py +3 -2
- nautobot/circuits/templates/circuits/circuit_create.html +3 -3
- nautobot/circuits/templates/circuits/circuittermination_create.html +9 -24
- nautobot/circuits/templates/circuits/inc/circuit_termination_cable_fragment.html +6 -6
- nautobot/circuits/templates/circuits/inc/speed_widget.html +12 -12
- nautobot/circuits/tests/integration/test_circuit.py +10 -13
- nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +0 -3
- nautobot/circuits/views.py +6 -2
- nautobot/cloud/filters.py +1 -1
- nautobot/cloud/navigation.py +3 -2
- nautobot/core/api/schema.py +1 -1
- nautobot/core/api/serializers.py +6 -1
- nautobot/core/api/urls.py +2 -0
- nautobot/core/api/views.py +12 -0
- nautobot/core/apps/__init__.py +11 -10
- nautobot/core/celery/__init__.py +3 -5
- nautobot/core/checks.py +46 -0
- nautobot/core/choices.py +1 -1
- nautobot/core/cli/bootstrap_v3_to_v5.py +105 -13
- nautobot/core/cli/migrate_deprecated_templates.py +227 -0
- nautobot/core/constants.py +3 -0
- nautobot/core/context_processors.py +9 -1
- nautobot/core/filters.py +4 -0
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +1 -1
- nautobot/core/forms/widgets.py +21 -2
- nautobot/core/jobs/__init__.py +62 -3
- nautobot/core/jobs/groups.py +31 -1
- nautobot/core/management/commands/generate_test_data.py +28 -9
- nautobot/core/models/__init__.py +11 -0
- nautobot/core/models/generics.py +9 -1
- nautobot/core/models/tree_queries.py +10 -5
- nautobot/core/models/utils.py +1 -1
- nautobot/core/settings.py +35 -19
- nautobot/core/settings.yaml +17 -33
- nautobot/core/signals.py +12 -1
- nautobot/core/tables.py +13 -6
- nautobot/core/templates/40x.html +1 -1
- nautobot/core/templates/500.html +2 -2
- nautobot/core/templates/admin/base.html +1 -2
- nautobot/core/templates/admin/change_list.html +9 -12
- nautobot/core/templates/admin/config/config.html +12 -12
- nautobot/core/templates/admin/index.html +3 -3
- nautobot/core/templates/base_django.html +1 -2
- nautobot/core/templates/buttons/export.html +1 -1
- nautobot/core/templates/components/button/dropdown.html +5 -3
- nautobot/core/templates/components/panel/body_wrapper_generic_table.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/components/panel/panel.html +3 -3
- nautobot/core/templates/components/tab/content_wrapper.html +6 -7
- nautobot/core/templates/components/tab/label_wrapper_distinct_view.html +1 -1
- nautobot/core/templates/echarts/echarts.html +22 -9
- nautobot/core/templates/generic/object_bulk_add_component.html +2 -1
- nautobot/core/templates/generic/object_bulk_create.html +6 -5
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_destroy.html +3 -3
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_bulk_remove.html +2 -2
- nautobot/core/templates/generic/object_bulk_update.html +5 -4
- nautobot/core/templates/generic/object_create.html +5 -4
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/generic/object_import.html +2 -1
- nautobot/core/templates/generic/object_list.html +12 -4
- nautobot/core/templates/generic/object_notes.html +5 -3
- nautobot/core/templates/generic/object_retrieve.html +4 -5
- nautobot/core/templates/graphene/graphiql.html +7 -8
- nautobot/core/templates/home.html +1 -1
- nautobot/core/templates/import_success.html +2 -1
- nautobot/core/templates/inc/computed_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/created_updated.html +7 -3
- nautobot/core/templates/inc/custom_fields/panel_data.html +1 -1
- nautobot/core/templates/inc/footer.html +3 -1
- nautobot/core/templates/inc/form_static_field.html +6 -0
- nautobot/core/templates/inc/header.html +11 -1
- nautobot/core/templates/inc/image_attachments.html +2 -1
- nautobot/core/templates/inc/media.html +14 -0
- nautobot/core/templates/inc/nav_menu.html +3 -9
- nautobot/core/templates/inc/object_details_advanced_panel.html +2 -2
- nautobot/core/templates/inc/search_panel.html +4 -4
- nautobot/core/templates/login.html +4 -2
- nautobot/core/templates/nautobot_config.py.j2 +6 -11
- nautobot/core/templates/redoc_ui.html +7 -0
- nautobot/core/templates/rest_framework/api.html +103 -2
- nautobot/core/templates/search.html +1 -1
- nautobot/core/templates/swagger_ui.html +17 -3
- nautobot/core/templates/system_jobs/import_objects.html +1 -2
- nautobot/core/templates/utilities/confirmation_form.html +2 -2
- nautobot/core/templates/utilities/obj_table.html +10 -2
- nautobot/core/templates/utilities/render_field.html +7 -7
- nautobot/core/templates/utilities/render_jinja2.html +2 -2
- nautobot/core/templates/utilities/templatetags/filter_form_drawer.html +37 -4
- nautobot/core/templates/utilities/theme_preview.html +19 -3
- nautobot/core/templates/widgets/number_input_with_choices.html +44 -0
- nautobot/core/templates/widgets/selectwithdisabled_option.html +3 -1
- nautobot/core/templatetags/helpers.py +76 -18
- nautobot/core/testing/api.py +68 -9
- nautobot/core/testing/filters.py +0 -23
- nautobot/core/testing/integration.py +41 -17
- nautobot/core/testing/mixins.py +2 -0
- nautobot/core/testing/utils.py +18 -4
- nautobot/core/testing/views.py +104 -13
- nautobot/core/tests/integration/test_app_home.py +34 -30
- nautobot/core/tests/integration/test_app_navbar.py +3 -0
- nautobot/core/tests/integration/test_filters.py +48 -11
- nautobot/core/tests/integration/test_theme.py +22 -21
- nautobot/core/tests/nautobot_config.py +3 -0
- nautobot/core/tests/nautobot_config_without_example_apps.py +4 -0
- nautobot/core/tests/runner.py +8 -1
- nautobot/core/tests/test_api.py +5 -3
- nautobot/core/tests/test_breadcrumbs.py +27 -28
- nautobot/core/tests/test_checks.py +28 -0
- nautobot/core/tests/test_cli.py +40 -0
- nautobot/core/tests/test_config.py +2 -1
- nautobot/core/tests/test_forms.py +55 -13
- nautobot/core/tests/test_jobs.py +144 -3
- nautobot/core/tests/test_nautobot_server.py +2 -0
- nautobot/core/tests/test_navigations.py +76 -1
- nautobot/core/tests/test_patch_social_django.py +42 -0
- nautobot/core/tests/test_renderers.py +59 -0
- nautobot/core/tests/test_settings_schema.py +1 -0
- nautobot/core/tests/test_tables.py +3 -1
- nautobot/core/tests/test_templatetags_helpers.py +62 -13
- nautobot/core/tests/test_templatetags_ui_framework.py +4 -4
- nautobot/core/tests/test_titles.py +0 -16
- nautobot/core/tests/test_tree_queries.py +14 -1
- nautobot/core/tests/test_ui.py +123 -4
- nautobot/core/tests/test_utils.py +72 -5
- nautobot/core/tests/test_views.py +159 -31
- nautobot/core/ui/breadcrumbs.py +70 -29
- nautobot/core/ui/bulk_buttons.py +1 -1
- nautobot/core/ui/choices.py +143 -27
- nautobot/core/ui/constants.py +76 -12
- nautobot/core/ui/echarts.py +15 -20
- nautobot/core/ui/object_detail.py +143 -55
- nautobot/core/ui/titles.py +3 -6
- nautobot/core/urls.py +20 -9
- nautobot/core/utils/cache.py +2 -1
- nautobot/core/utils/filtering.py +28 -18
- nautobot/core/utils/lookup.py +49 -8
- nautobot/core/utils/module_loading.py +21 -0
- nautobot/core/utils/patch_social_django.py +128 -0
- nautobot/core/views/__init__.py +38 -1
- nautobot/core/views/generic.py +3 -3
- nautobot/core/views/mixins.py +45 -22
- nautobot/core/views/renderers.py +4 -3
- nautobot/core/views/viewsets.py +2 -1
- nautobot/data_validation/apps.py +1 -5
- nautobot/data_validation/custom_validators.py +4 -4
- nautobot/data_validation/filters.py +1 -1
- nautobot/data_validation/forms.py +40 -0
- nautobot/data_validation/migrations/0001_initial.py +0 -7
- nautobot/data_validation/migrations/0002_data_migration_from_app.py +3 -14
- nautobot/data_validation/models.py +16 -7
- nautobot/data_validation/navigation.py +8 -1
- nautobot/data_validation/tables.py +12 -5
- nautobot/data_validation/templates/data_validation/datacompliance_tab.html +1 -0
- nautobot/data_validation/templates/data_validation/device_constraints.html +61 -0
- nautobot/data_validation/tests/__init__.py +2 -2
- nautobot/data_validation/tests/migrations/test_migrations.py +83 -3
- nautobot/data_validation/tests/test_data_compliance_rules.py +12 -7
- nautobot/data_validation/tests/test_filters.py +8 -6
- nautobot/data_validation/tests/test_models.py +15 -0
- nautobot/data_validation/tests/test_views.py +190 -32
- nautobot/data_validation/urls.py +2 -5
- nautobot/data_validation/views.py +73 -40
- nautobot/dcim/api/serializers.py +3 -13
- nautobot/dcim/apps.py +4 -0
- nautobot/dcim/choices.py +65 -0
- nautobot/dcim/constants.py +7 -0
- nautobot/dcim/custom_validators.py +84 -0
- nautobot/dcim/factory.py +1 -1
- nautobot/dcim/filter_mixins.py +353 -4
- nautobot/dcim/{filters/__init__.py → filters.py} +15 -36
- nautobot/dcim/forms.py +90 -4
- nautobot/dcim/migrations/0075_interface_duplex_interface_speed_and_more.py +32 -0
- nautobot/dcim/migrations/{0075_add_deviceclusterassignment.py → 0076_add_deviceclusterassignment.py} +1 -1
- nautobot/dcim/migrations/{0076_device_cluster_to_clusters_data_migration.py → 0077_device_cluster_to_clusters_data_migration.py} +1 -1
- nautobot/dcim/migrations/{0077_remove_device_cluster.py → 0078_remove_device_cluster.py} +1 -1
- nautobot/dcim/migrations/0079_remove_device_location_tenant_name_uniqueness.py +16 -0
- nautobot/dcim/migrations/0080_device_name_data_migration.py +59 -0
- nautobot/dcim/migrations/0081_alter_device_device_redundancy_group_priority_and_more.py +25 -0
- nautobot/dcim/models/device_component_templates.py +33 -1
- nautobot/dcim/models/device_components.py +98 -64
- nautobot/dcim/models/devices.py +30 -20
- nautobot/dcim/navigation.py +7 -6
- nautobot/dcim/tables/devices.py +18 -0
- nautobot/dcim/tables/devicetypes.py +8 -1
- nautobot/dcim/tables/racks.py +0 -2
- nautobot/dcim/tables/template_code.py +15 -15
- nautobot/dcim/templates/dcim/cable_connect.html +28 -112
- nautobot/dcim/templates/dcim/cable_trace.html +0 -4
- nautobot/dcim/templates/dcim/{cable_edit.html → cable_update.html} +1 -1
- nautobot/dcim/templates/dcim/consoleport.html +7 -6
- nautobot/dcim/templates/dcim/consoleserverport.html +7 -6
- nautobot/dcim/templates/dcim/device/config.html +2 -2
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +1 -1
- nautobot/dcim/templates/dcim/device/status.html +8 -8
- nautobot/dcim/templates/dcim/device.html +1 -1
- nautobot/dcim/templates/dcim/device_component_add.html +2 -2
- nautobot/dcim/templates/dcim/device_create.html +5 -3
- nautobot/dcim/templates/dcim/device_interface_delete.html +1 -1
- nautobot/dcim/templates/dcim/device_list.html +73 -10
- nautobot/dcim/templates/dcim/devicebay.html +1 -1
- nautobot/dcim/templates/dcim/devicebay_populate.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_component_add.html +2 -2
- nautobot/dcim/templates/dcim/footer_convert_to_contact_or_team_record.html +14 -0
- nautobot/dcim/templates/dcim/frontport.html +10 -9
- nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/edit_form_softwareversion_js.html +2 -2
- nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +1 -1
- nautobot/dcim/templates/dcim/inc/rack_elevation.html +1 -1
- nautobot/dcim/templates/dcim/interface.html +35 -7
- nautobot/dcim/templates/dcim/interface_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/interface_edit.html +2 -0
- nautobot/dcim/templates/dcim/inventoryitem.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_add.html +3 -1
- nautobot/dcim/templates/dcim/inventoryitem_bulk_delete.html +1 -1
- nautobot/dcim/templates/dcim/inventoryitem_edit.html +3 -1
- nautobot/dcim/templates/dcim/module/base.html +49 -9
- nautobot/dcim/templates/dcim/module_consoleports.html +1 -1
- nautobot/dcim/templates/dcim/module_consoleserverports.html +1 -1
- nautobot/dcim/templates/dcim/module_frontports.html +1 -1
- nautobot/dcim/templates/dcim/module_interfaces.html +1 -1
- nautobot/dcim/templates/dcim/module_list.html +57 -8
- nautobot/dcim/templates/dcim/module_modulebays.html +1 -1
- nautobot/dcim/templates/dcim/module_poweroutlets.html +1 -1
- nautobot/dcim/templates/dcim/module_powerports.html +1 -1
- nautobot/dcim/templates/dcim/module_rearports.html +1 -1
- nautobot/dcim/templates/dcim/modulefamily_retrieve.html +1 -1
- nautobot/dcim/templates/dcim/moduletype_list.html +2 -2
- nautobot/dcim/templates/dcim/moduletype_retrieve.html +49 -9
- nautobot/dcim/templates/dcim/platform_create.html +1 -1
- nautobot/dcim/templates/dcim/poweroutlet.html +1 -1
- nautobot/dcim/templates/dcim/powerport.html +6 -5
- nautobot/dcim/templates/dcim/rack_elevation_list.html +17 -5
- nautobot/dcim/templates/dcim/rack_retrieve.html +22 -15
- nautobot/dcim/templates/dcim/rearport.html +8 -7
- nautobot/dcim/templates/dcim/trace/cable.html +1 -1
- nautobot/dcim/templates/dcim/virtualchassis_add_member.html +16 -14
- nautobot/dcim/templates/dcim/virtualchassis_update.html +15 -7
- nautobot/dcim/tests/integration/test_controller.py +4 -6
- nautobot/dcim/tests/integration/test_controller_managed_device_group.py +1 -5
- nautobot/dcim/tests/integration/test_create_device.py +0 -2
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +1 -3
- nautobot/dcim/tests/integration/test_fileinputpicker.py +6 -10
- nautobot/dcim/tests/integration/test_location_bulk_operations.py +0 -2
- nautobot/dcim/tests/integration/test_module_bay_position.py +3 -4
- nautobot/dcim/tests/test_api.py +194 -6
- nautobot/dcim/tests/test_custom_validators.py +229 -0
- nautobot/dcim/tests/test_filters.py +55 -7
- nautobot/dcim/tests/test_forms.py +110 -8
- nautobot/dcim/tests/test_graphql.py +44 -1
- nautobot/dcim/tests/test_models.py +328 -4
- nautobot/dcim/tests/test_tables.py +160 -0
- nautobot/dcim/tests/test_views.py +132 -29
- nautobot/dcim/urls.py +64 -21
- nautobot/dcim/utils.py +3 -3
- nautobot/dcim/views.py +777 -397
- nautobot/extras/api/views.py +60 -45
- nautobot/extras/choices.py +2 -13
- nautobot/extras/datasources/git.py +3 -1
- nautobot/extras/{filters/mixins.py → filter_mixins.py} +1 -1
- nautobot/extras/{filters/customfields.py → filter_mixins_customfields.py} +42 -6
- nautobot/extras/{filters/__init__.py → filters.py} +33 -48
- nautobot/extras/forms/forms.py +14 -15
- nautobot/extras/forms/mixins.py +0 -41
- nautobot/extras/jobs.py +2 -0
- nautobot/extras/jobs_ui.py +4 -3
- nautobot/extras/management/__init__.py +11 -0
- nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +4 -1
- nautobot/extras/migrations/0127_approval_workflow_models.py +6 -6
- nautobot/extras/migrations/0129_jobresult_debug_log_count_jobresult_error_log_count_and_more.py +37 -0
- nautobot/extras/migrations/0130_jobresult_generate_log_entry_counts.py +42 -0
- nautobot/extras/migrations/0131_configcontext_device_families.py +18 -0
- nautobot/extras/models/__init__.py +1 -2
- nautobot/extras/models/approvals.py +33 -14
- nautobot/extras/models/change_logging.py +4 -0
- nautobot/extras/models/contacts.py +2 -0
- nautobot/extras/models/groups.py +44 -5
- nautobot/extras/models/jobs.py +60 -4
- nautobot/extras/models/mixins.py +28 -0
- nautobot/extras/models/models.py +23 -2
- nautobot/extras/models/secrets.py +1 -0
- nautobot/extras/models/statuses.py +0 -15
- nautobot/extras/navigation.py +13 -9
- nautobot/extras/plugins/__init__.py +33 -55
- nautobot/extras/plugins/marketplace_manifest.yml +49 -1
- nautobot/extras/plugins/tables.py +3 -3
- nautobot/extras/plugins/urls.py +2 -21
- nautobot/extras/plugins/utils.py +1 -33
- nautobot/extras/plugins/views.py +0 -9
- nautobot/extras/querysets.py +8 -0
- nautobot/extras/signals.py +20 -19
- nautobot/extras/tables.py +64 -68
- nautobot/extras/templates/django_ajax_tables/ajax_wrapper.html +2 -0
- nautobot/extras/templates/extras/approval_dashboard.html +7 -5
- nautobot/extras/templates/extras/approvalworkflowdefinition_update.html +4 -2
- nautobot/extras/templates/extras/approvalworkflowstage_retrieve.html +20 -12
- nautobot/extras/templates/extras/configcontext_update.html +1 -0
- nautobot/extras/templates/extras/configcontextschema_validation.html +2 -2
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +11 -5
- nautobot/extras/templates/extras/dynamicgroup_update.html +1 -1
- nautobot/extras/templates/extras/gitrepository_result.html +0 -2
- nautobot/extras/templates/extras/inc/approval_buttons_column.html +20 -6
- nautobot/extras/templates/extras/inc/bulk_edit_overridable_field.html +8 -7
- nautobot/extras/templates/extras/inc/configcontext_format.html +10 -3
- nautobot/extras/templates/extras/inc/graphqlquery_execute.html +71 -0
- nautobot/extras/templates/extras/inc/job_tiles.html +15 -3
- nautobot/extras/templates/extras/inc/json_format.html +10 -3
- nautobot/extras/templates/extras/inc/overridable_field.html +13 -12
- nautobot/extras/templates/extras/job.html +29 -12
- nautobot/extras/templates/extras/job_bulk_edit.html +18 -0
- nautobot/extras/templates/extras/job_edit.html +52 -46
- nautobot/extras/templates/extras/job_list.html +29 -25
- nautobot/extras/templates/extras/marketplace.html +5 -9
- nautobot/extras/templates/extras/object_configcontext.html +1 -1
- nautobot/extras/templates/extras/object_dynamicgroups.html +2 -2
- nautobot/extras/templates/extras/objectchange_retrieve.html +19 -39
- nautobot/extras/templates/extras/plugin_detail.html +29 -24
- nautobot/extras/templates/extras/plugins_list.html +16 -26
- nautobot/extras/templates/extras/role_retrieve.html +64 -0
- nautobot/extras/templates/extras/scheduledjob.html +4 -2
- nautobot/extras/templates/extras/secret_create.html +1 -1
- nautobot/extras/templatetags/custom_links.py +12 -12
- nautobot/extras/templatetags/job_buttons.py +14 -12
- nautobot/extras/test_jobs/invalid_import.py +9 -0
- nautobot/extras/test_jobs/log_counts_by_level.py +23 -0
- nautobot/extras/test_jobs/missing_import.py +11 -0
- nautobot/extras/tests/integration/test_computedfields.py +8 -9
- nautobot/extras/tests/integration/test_configcontextschema.py +27 -26
- nautobot/extras/tests/integration/test_customfields.py +9 -10
- nautobot/extras/tests/integration/test_dynamicgroups.py +12 -9
- nautobot/extras/tests/integration/test_plugin_banner.py +3 -0
- nautobot/extras/tests/integration/test_plugins.py +18 -6
- nautobot/extras/tests/integration/test_relationships.py +0 -2
- nautobot/extras/tests/test_api.py +90 -18
- nautobot/extras/tests/test_approvals.py +38 -38
- nautobot/extras/tests/test_changelog.py +59 -5
- nautobot/extras/tests/test_customfields.py +22 -13
- nautobot/extras/tests/test_customfields_filters.py +479 -0
- nautobot/extras/tests/test_dynamicgroups.py +39 -1
- nautobot/extras/tests/test_filters.py +57 -22
- nautobot/extras/tests/test_forms.py +18 -21
- nautobot/extras/tests/test_jobs.py +25 -4
- nautobot/extras/tests/test_migrations.py +1 -0
- nautobot/extras/tests/test_models.py +51 -33
- nautobot/extras/tests/test_plugins.py +36 -10
- nautobot/extras/tests/test_utils.py +3 -4
- nautobot/extras/tests/test_views.py +52 -112
- nautobot/extras/urls.py +0 -14
- nautobot/extras/views.py +164 -71
- nautobot/ipam/factory.py +7 -0
- nautobot/ipam/filter_mixins.py +38 -0
- nautobot/ipam/filters.py +53 -38
- nautobot/ipam/formfields.py +1 -1
- nautobot/ipam/forms.py +6 -3
- nautobot/ipam/migrations/0030_ipam__namespaces.py +13 -0
- nautobot/ipam/migrations/0031_ipam___data_migrations.py +4 -1
- nautobot/ipam/migrations/0054_namespace_tenant.py +25 -0
- nautobot/ipam/models.py +29 -2
- nautobot/ipam/navigation.py +3 -2
- nautobot/ipam/signals.py +71 -0
- nautobot/ipam/tables.py +19 -6
- nautobot/ipam/templates/ipam/inc/toggle_available.html +10 -10
- nautobot/ipam/templates/ipam/inc/vlangroup_header.html +1 -0
- nautobot/ipam/templates/ipam/ipaddress.html +14 -0
- nautobot/ipam/templates/ipam/ipaddress_merge.html +3 -3
- nautobot/ipam/templates/ipam/ipaddresstointerface_retrieve.html +1 -0
- nautobot/ipam/templates/ipam/namespace_ip_addresses.html +1 -1
- nautobot/ipam/templates/ipam/namespace_prefixes.html +1 -1
- nautobot/ipam/templates/ipam/namespace_update.html +15 -0
- nautobot/ipam/templates/ipam/namespace_vrfs.html +1 -1
- nautobot/ipam/templates/ipam/prefix_delete.html +1 -1
- nautobot/ipam/templates/ipam/prefix_list.html +14 -13
- nautobot/ipam/templates/ipam/vlan_interfaces.html +1 -1
- nautobot/ipam/templates/ipam/vlan_vminterfaces.html +1 -1
- nautobot/ipam/tests/migration/test_migrations.py +89 -0
- nautobot/ipam/tests/test_api.py +13 -6
- nautobot/ipam/tests/test_filters.py +36 -1
- nautobot/ipam/tests/test_forms.py +1 -1
- nautobot/ipam/tests/test_models.py +44 -2
- nautobot/ipam/tests/test_tables.py +1 -2
- nautobot/ipam/tests/test_utils.py +1 -1
- nautobot/ipam/tests/test_views.py +13 -14
- nautobot/ipam/ui.py +0 -17
- nautobot/ipam/utils/migrations.py +16 -2
- nautobot/ipam/utils/testing.py +9 -3
- nautobot/ipam/views.py +53 -11
- nautobot/load_balancers/__init__.py +0 -0
- nautobot/load_balancers/api/__init__.py +1 -0
- nautobot/load_balancers/api/serializers.py +75 -0
- nautobot/load_balancers/api/urls.py +23 -0
- nautobot/load_balancers/api/views.py +61 -0
- nautobot/load_balancers/apps.py +17 -0
- nautobot/load_balancers/choices.py +167 -0
- nautobot/load_balancers/filters.py +225 -0
- nautobot/load_balancers/forms.py +532 -0
- nautobot/load_balancers/management/commands/__init__.py +0 -0
- nautobot/load_balancers/management/commands/generate_load_balancer_models_test_data.py +38 -0
- nautobot/load_balancers/migrations/0001_initial.py +465 -0
- nautobot/load_balancers/migrations/0002_create_default_statuses_pool_members.py +31 -0
- nautobot/load_balancers/migrations/__init__.py +0 -0
- nautobot/load_balancers/models.py +423 -0
- nautobot/load_balancers/navigation.py +80 -0
- nautobot/load_balancers/tables.py +255 -0
- nautobot/load_balancers/tests/__init__.py +474 -0
- nautobot/load_balancers/tests/test_api.py +353 -0
- nautobot/load_balancers/tests/test_filters.py +134 -0
- nautobot/load_balancers/tests/test_forms.py +266 -0
- nautobot/load_balancers/tests/test_models.py +195 -0
- nautobot/load_balancers/tests/test_views.py +229 -0
- nautobot/load_balancers/urls.py +17 -0
- nautobot/load_balancers/views.py +248 -0
- nautobot/project-static/dist/css/github-dark.min.css +10 -0
- nautobot/project-static/dist/css/github.min.css +10 -0
- nautobot/project-static/dist/css/nautobot.css +1 -11
- nautobot/project-static/dist/css/nautobot.css.map +1 -1
- nautobot/project-static/dist/js/libraries.js +1 -1
- nautobot/project-static/dist/js/libraries.js.map +1 -1
- nautobot/project-static/dist/js/nautobot.js +1 -1
- nautobot/project-static/dist/js/nautobot.js.map +1 -1
- nautobot/project-static/js/cabletrace.js +1 -1
- nautobot/project-static/js/forms.js +13 -0
- nautobot/project-static/js/interface_filtering.js +20 -16
- nautobot/project-static/nautobot-icons/battery-3.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-globe.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield-check.svg +3 -0
- nautobot/project-static/nautobot-icons/bus-shield.svg +3 -0
- nautobot/project-static/nautobot-icons/cloud.svg +1 -1
- nautobot/project-static/nautobot-icons/control-panel.svg +1 -1
- nautobot/project-static/nautobot-icons/device-lifecycle.svg +1 -1
- nautobot/project-static/nautobot-icons/elements.svg +1 -1
- nautobot/project-static/nautobot-icons/extensibility.svg +3 -0
- nautobot/project-static/nautobot-icons/hammer.svg +1 -1
- nautobot/project-static/nautobot-icons/organization.svg +3 -0
- nautobot/project-static/nautobot-icons/secrets.svg +1 -1
- nautobot/project-static/nautobot-icons/security.svg +3 -0
- nautobot/project-static/nautobot-icons/server.svg +1 -1
- nautobot/project-static/nautobot-icons/star-filled.svg +1 -1
- nautobot/project-static/nautobot-icons/star.svg +1 -1
- nautobot/tenancy/api/serializers.py +1 -0
- nautobot/tenancy/api/views.py +2 -1
- nautobot/tenancy/{filters/__init__.py → filters.py} +2 -10
- nautobot/tenancy/navigation.py +3 -1
- nautobot/tenancy/tests/test_filters.py +0 -2
- nautobot/tenancy/views.py +2 -1
- nautobot/ui/package-lock.json +87 -4
- nautobot/ui/package.json +2 -1
- nautobot/ui/src/js/collapse.js +3 -3
- nautobot/ui/src/js/nautobot.js +16 -1
- nautobot/ui/src/js/select2.js +53 -2
- nautobot/ui/src/scss/colors.scss +1 -1
- nautobot/ui/src/scss/nautobot.scss +112 -30
- nautobot/ui/webpack.config.js +13 -0
- nautobot/users/templates/users/preferences.html +11 -2
- nautobot/users/templates/users/profile.html +45 -12
- nautobot/users/templates/users/sessionkey_delete.html +1 -1
- nautobot/users/tests/test_api.py +4 -0
- nautobot/users/views.py +4 -2
- nautobot/virtualization/filters.py +6 -1
- nautobot/virtualization/models.py +1 -68
- nautobot/virtualization/navigation.py +3 -2
- nautobot/virtualization/templates/virtualization/virtual_machine_vminterface_delete.html +1 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_list.html +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +3 -1
- nautobot/virtualization/tests/test_api.py +3 -0
- nautobot/virtualization/tests/test_filters.py +10 -1
- nautobot/virtualization/tests/test_models.py +45 -4
- nautobot/virtualization/views.py +4 -1
- nautobot/vpn/__init__.py +0 -0
- nautobot/vpn/api/serializers.py +113 -0
- nautobot/vpn/api/urls.py +19 -0
- nautobot/vpn/api/views.py +70 -0
- nautobot/vpn/apps.py +8 -0
- nautobot/vpn/choices.py +171 -0
- nautobot/vpn/factory.py +219 -0
- nautobot/vpn/filters.py +234 -0
- nautobot/vpn/forms.py +487 -0
- nautobot/vpn/homepage.py +19 -0
- nautobot/vpn/migrations/0001_initial.py +541 -0
- nautobot/vpn/migrations/0002_populate_defaults.py +199 -0
- nautobot/vpn/migrations/__init__.py +0 -0
- nautobot/vpn/models.py +535 -0
- nautobot/vpn/navigation.py +98 -0
- nautobot/vpn/tables.py +383 -0
- nautobot/vpn/templates/vpn/vpnprofile_create.html +150 -0
- nautobot/vpn/tests/__init__.py +0 -0
- nautobot/vpn/tests/test_api.py +336 -0
- nautobot/vpn/tests/test_filters.py +139 -0
- nautobot/vpn/tests/test_forms.py +293 -0
- nautobot/vpn/tests/test_models.py +147 -0
- nautobot/vpn/tests/test_views.py +300 -0
- nautobot/vpn/urls.py +16 -0
- nautobot/vpn/views.py +495 -0
- nautobot/wireless/navigation.py +3 -2
- nautobot/wireless/tests/integration/test_radio_profile.py +1 -5
- nautobot/wireless/tests/test_api.py +1 -1
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/METADATA +15 -15
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/RECORD +514 -572
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/entry_points.txt +1 -0
- nautobot/circuits/templates/circuits/circuit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuit_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuit_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittermination.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_edit.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/circuittermination_update.html +0 -1
- nautobot/circuits/templates/circuits/circuittype.html +0 -2
- nautobot/circuits/templates/circuits/circuittype_retrieve.html +0 -2
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +0 -85
- nautobot/circuits/templates/circuits/provider.html +0 -2
- nautobot/circuits/templates/circuits/provider_edit.html +0 -2
- nautobot/circuits/templates/circuits/provider_retrieve.html +0 -1
- nautobot/circuits/templates/circuits/provider_update.html +0 -1
- nautobot/circuits/templates/circuits/providernetwork.html +0 -2
- nautobot/circuits/templates/circuits/providernetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +0 -2
- nautobot/cloud/templates/cloud/cloudservice_retrieve.html +0 -2
- nautobot/core/templates/buttons/import.html +0 -9
- nautobot/data_validation/template_content.py +0 -42
- nautobot/data_validation/templates/data_validation/datacompliance_retrieve.html +0 -1
- nautobot/dcim/filters/mixins.py +0 -354
- nautobot/dcim/templates/dcim/controller/base.html +0 -2
- nautobot/dcim/templates/dcim/controller_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/controller_wirelessnetworks.html +0 -2
- nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/device/base.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleports.html +0 -2
- nautobot/dcim/templates/dcim/device/consoleserverports.html +0 -2
- nautobot/dcim/templates/dcim/device/devicebays.html +0 -2
- nautobot/dcim/templates/dcim/device/frontports.html +0 -2
- nautobot/dcim/templates/dcim/device/interfaces.html +0 -2
- nautobot/dcim/templates/dcim/device/inventory.html +0 -2
- nautobot/dcim/templates/dcim/device/modulebays.html +0 -2
- nautobot/dcim/templates/dcim/device/poweroutlets.html +0 -2
- nautobot/dcim/templates/dcim/device/powerports.html +0 -2
- nautobot/dcim/templates/dcim/device/rearports.html +0 -2
- nautobot/dcim/templates/dcim/device/wireless.html +0 -2
- nautobot/dcim/templates/dcim/device_component.html +0 -2
- nautobot/dcim/templates/dcim/device_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicefamily_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/devicetype.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_edit.html +0 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/inc/device_napalm_tabs.html +0 -1
- nautobot/dcim/templates/dcim/interfaceredundancygroup_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/location.html +0 -2
- nautobot/dcim/templates/dcim/location_edit.html +0 -2
- nautobot/dcim/templates/dcim/location_retrieve.html +0 -243
- nautobot/dcim/templates/dcim/locationtype.html +0 -2
- nautobot/dcim/templates/dcim/locationtype_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/manufacturer.html +0 -2
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -1
- nautobot/dcim/templates/dcim/platform.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed.html +0 -2
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_edit.html +0 -2
- nautobot/dcim/templates/dcim/powerpanel_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/rack.html +0 -2
- nautobot/dcim/templates/dcim/rack_edit.html +0 -2
- nautobot/dcim/templates/dcim/rackgroup.html +0 -2
- nautobot/dcim/templates/dcim/rackreservation.html +0 -2
- nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/softwareversion_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_add.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_edit.html +0 -2
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +0 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -2
- nautobot/dcim/ui.py +0 -29
- nautobot/extras/templates/extras/computedfield.html +0 -2
- nautobot/extras/templates/extras/computedfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontext.html +0 -2
- nautobot/extras/templates/extras/configcontext_edit.html +0 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +0 -2
- nautobot/extras/templates/extras/configcontextschema.html +0 -2
- nautobot/extras/templates/extras/configcontextschema_edit.html +0 -2
- nautobot/extras/templates/extras/contact_retrieve.html +0 -2
- nautobot/extras/templates/extras/customfield.html +0 -2
- nautobot/extras/templates/extras/customfield_edit.html +0 -2
- nautobot/extras/templates/extras/customfield_retrieve.html +0 -2
- nautobot/extras/templates/extras/customlink.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup.html +0 -2
- nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -2
- nautobot/extras/templates/extras/exporttemplate.html +0 -2
- nautobot/extras/templates/extras/gitrepository.html +0 -2
- nautobot/extras/templates/extras/gitrepository_object_edit.html +0 -2
- nautobot/extras/templates/extras/graphqlquery.html +0 -2
- nautobot/extras/templates/extras/graphqlquery_list.html +0 -1
- nautobot/extras/templates/extras/graphqlquery_retrieve.html +0 -97
- nautobot/extras/templates/extras/job_detail.html +0 -2
- nautobot/extras/templates/extras/jobbutton_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobhook.html +0 -2
- nautobot/extras/templates/extras/jobqueue_retrieve.html +0 -2
- nautobot/extras/templates/extras/jobresult.html +0 -2
- nautobot/extras/templates/extras/metadatatype_retrieve.html +0 -2
- nautobot/extras/templates/extras/note.html +0 -2
- nautobot/extras/templates/extras/note_retrieve.html +0 -1
- nautobot/extras/templates/extras/object_changelog.html +0 -2
- nautobot/extras/templates/extras/object_notes.html +0 -2
- nautobot/extras/templates/extras/objectchange.html +0 -2
- nautobot/extras/templates/extras/objectchange_list.html +0 -3
- nautobot/extras/templates/extras/relationship.html +0 -1
- nautobot/extras/templates/extras/secret.html +0 -1
- nautobot/extras/templates/extras/secret_edit.html +0 -1
- nautobot/extras/templates/extras/secretsgroup.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_edit.html +0 -2
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +0 -2
- nautobot/extras/templates/extras/status.html +0 -2
- nautobot/extras/templates/extras/tag.html +0 -2
- nautobot/extras/templates/extras/tag_edit.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +0 -2
- nautobot/extras/templates/extras/team_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/inc/prefix_header_extra_content_table.html +0 -4
- nautobot/ipam/templates/ipam/namespace_retrieve.html +0 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -2
- nautobot/ipam/templates/ipam/prefix_edit.html +0 -1
- nautobot/ipam/templates/ipam/prefix_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/rir.html +0 -2
- nautobot/ipam/templates/ipam/routetarget.html +0 -1
- nautobot/ipam/templates/ipam/service.html +0 -2
- nautobot/ipam/templates/ipam/service_edit.html +0 -2
- nautobot/ipam/templates/ipam/service_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlan.html +0 -2
- nautobot/ipam/templates/ipam/vlan_edit.html +0 -2
- nautobot/ipam/templates/ipam/vlan_retrieve.html +0 -2
- nautobot/ipam/templates/ipam/vlangroup.html +0 -2
- nautobot/ipam/templates/ipam/vrf.html +0 -1
- nautobot/tenancy/templates/tenancy/tenant.html +0 -2
- nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup.html +0 -2
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +0 -1
- nautobot/virtualization/templates/virtualization/clustergroup.html +0 -2
- nautobot/virtualization/templates/virtualization/clustertype.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +0 -2
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/radioprofile_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/supporteddatarate_retrieve.html +0 -2
- nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +0 -2
- /nautobot/dcim/templates/dcim/{cable.html → cable_retrieve.html} +0 -0
- /nautobot/tenancy/{filters/mixins.py → filter_mixins.py} +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/LICENSE.txt +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/NOTICE +0 -0
- {nautobot-3.0.0a2.dist-info → nautobot-3.0.0rc1.dist-info}/WHEEL +0 -0
nautobot/core/tests/test_jobs.py
CHANGED
|
@@ -16,12 +16,13 @@ from nautobot.core.jobs import ExportObjectList
|
|
|
16
16
|
from nautobot.core.jobs.cleanup import CleanupTypes
|
|
17
17
|
from nautobot.core.testing import create_job_result_and_run_job, TransactionTestCase
|
|
18
18
|
from nautobot.core.testing.context import load_event_broker_override_settings
|
|
19
|
-
from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Manufacturer
|
|
20
|
-
from nautobot.extras.choices import JobResultStatusChoices, LogLevelChoices
|
|
19
|
+
from nautobot.dcim.models import Device, DeviceType, FrontPortTemplate, Location, LocationType, Manufacturer
|
|
20
|
+
from nautobot.extras.choices import DynamicGroupTypeChoices, JobResultStatusChoices, LogLevelChoices
|
|
21
21
|
from nautobot.extras.factory import JobResultFactory, ObjectChangeFactory
|
|
22
22
|
from nautobot.extras.models import (
|
|
23
23
|
Contact,
|
|
24
24
|
ContactAssociation,
|
|
25
|
+
DynamicGroup,
|
|
25
26
|
ExportTemplate,
|
|
26
27
|
FileProxy,
|
|
27
28
|
JobLogEntry,
|
|
@@ -34,7 +35,7 @@ from nautobot.extras.models import (
|
|
|
34
35
|
)
|
|
35
36
|
from nautobot.extras.models.metadata import ObjectMetadata
|
|
36
37
|
from nautobot.ipam.models import IPAddress, Namespace, Prefix
|
|
37
|
-
from nautobot.users.models import ObjectPermission
|
|
38
|
+
from nautobot.users.models import ObjectPermission, User
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class ExportObjectListTest(TransactionTestCase):
|
|
@@ -1248,3 +1249,143 @@ class BulkDeleteTestCase(TransactionTestCase):
|
|
|
1248
1249
|
saved_view_id=None,
|
|
1249
1250
|
)
|
|
1250
1251
|
self._common_no_error_test_assertion(Role, job_result, name__istartswith="Example Status")
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
class RefreshDynamicGroupCacheJobButtonReceiverTestCase(TransactionTestCase):
|
|
1255
|
+
job_module = "nautobot.core.jobs.groups"
|
|
1256
|
+
job_name = "RefreshDynamicGroupCacheJobButtonReceiver"
|
|
1257
|
+
|
|
1258
|
+
def test_successful_cache_refresh(self):
|
|
1259
|
+
LocationType.objects.create(name="DG Test LT 1")
|
|
1260
|
+
LocationType.objects.create(name="DG Test LT 2")
|
|
1261
|
+
LocationType.objects.create(name="DG Test LT 3")
|
|
1262
|
+
dg = DynamicGroup(
|
|
1263
|
+
name="Location Types",
|
|
1264
|
+
content_type=ContentType.objects.get_for_model(LocationType),
|
|
1265
|
+
group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER,
|
|
1266
|
+
filter={"name__isw": ["DG Test"]},
|
|
1267
|
+
)
|
|
1268
|
+
dg.clean()
|
|
1269
|
+
dg.save(update_cached_members=False)
|
|
1270
|
+
self.assertEqual(0, dg.count)
|
|
1271
|
+
|
|
1272
|
+
job_result = create_job_result_and_run_job(
|
|
1273
|
+
self.job_module,
|
|
1274
|
+
self.job_name,
|
|
1275
|
+
object_model_name="extras.dynamicgroup",
|
|
1276
|
+
object_pk=dg.pk,
|
|
1277
|
+
)
|
|
1278
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1279
|
+
self.assertEqual(3, dg.count)
|
|
1280
|
+
|
|
1281
|
+
dg.filter = {"name__iew": ["DG Test"]}
|
|
1282
|
+
dg.clean()
|
|
1283
|
+
dg.save(update_cached_members=False)
|
|
1284
|
+
self.assertEqual(3, dg.count)
|
|
1285
|
+
job_result = create_job_result_and_run_job(
|
|
1286
|
+
self.job_module,
|
|
1287
|
+
self.job_name,
|
|
1288
|
+
object_model_name="extras.dynamicgroup",
|
|
1289
|
+
object_pk=dg.pk,
|
|
1290
|
+
)
|
|
1291
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1292
|
+
self.assertEqual(0, dg.count)
|
|
1293
|
+
|
|
1294
|
+
def test_failure_on_non_dg(self):
|
|
1295
|
+
job_result = create_job_result_and_run_job(
|
|
1296
|
+
self.job_module,
|
|
1297
|
+
self.job_name,
|
|
1298
|
+
object_model_name="extras.status",
|
|
1299
|
+
object_pk=Status.objects.first().pk,
|
|
1300
|
+
)
|
|
1301
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1302
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1303
|
+
self.assertEqual(log_fail.message, "This job button should only be used with Dynamic Group records.")
|
|
1304
|
+
|
|
1305
|
+
def test_failure_on_static_dg(self):
|
|
1306
|
+
dg = DynamicGroup.objects.create(
|
|
1307
|
+
name="Location Types",
|
|
1308
|
+
content_type=ContentType.objects.get_for_model(LocationType),
|
|
1309
|
+
group_type=DynamicGroupTypeChoices.TYPE_STATIC,
|
|
1310
|
+
)
|
|
1311
|
+
job_result = create_job_result_and_run_job(
|
|
1312
|
+
self.job_module,
|
|
1313
|
+
self.job_name,
|
|
1314
|
+
object_model_name="extras.dynamicgroup",
|
|
1315
|
+
object_pk=dg.pk,
|
|
1316
|
+
)
|
|
1317
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1318
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1319
|
+
self.assertEqual(
|
|
1320
|
+
log_fail.message,
|
|
1321
|
+
"The members of this Dynamic Group are statically defined and do not need to be recalculated.",
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
class ValidateModelDataTestCase(TransactionTestCase):
|
|
1326
|
+
job_module = "nautobot.core.jobs"
|
|
1327
|
+
job_name = "ValidateModelData"
|
|
1328
|
+
|
|
1329
|
+
def test_successful_validation(self):
|
|
1330
|
+
job_result = create_job_result_and_run_job(
|
|
1331
|
+
self.job_module,
|
|
1332
|
+
self.job_name,
|
|
1333
|
+
content_types=[ContentType.objects.get_for_model(Status).pk],
|
|
1334
|
+
verbose=True,
|
|
1335
|
+
)
|
|
1336
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1337
|
+
|
|
1338
|
+
def test_failure_on_invalid_dg(self):
|
|
1339
|
+
dg = DynamicGroup(
|
|
1340
|
+
name="Legacy rear_port_template filter",
|
|
1341
|
+
filter={"rear_port_template": "74aac78c-fabb-468c-a036-26c46c56f27a"},
|
|
1342
|
+
content_type=ContentType.objects.get_for_model(FrontPortTemplate),
|
|
1343
|
+
)
|
|
1344
|
+
dg.save(update_cached_members=False)
|
|
1345
|
+
job_result = create_job_result_and_run_job(
|
|
1346
|
+
self.job_module,
|
|
1347
|
+
self.job_name,
|
|
1348
|
+
content_types=[ContentType.objects.get_for_model(DynamicGroup).pk],
|
|
1349
|
+
verbose=False,
|
|
1350
|
+
)
|
|
1351
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1352
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1353
|
+
self.assertIn("Enter a list of values", log_fail.message)
|
|
1354
|
+
|
|
1355
|
+
def test_warning_without_permission(self):
|
|
1356
|
+
job_result = create_job_result_and_run_job(
|
|
1357
|
+
self.job_module,
|
|
1358
|
+
self.job_name,
|
|
1359
|
+
username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
|
|
1360
|
+
content_types=[ContentType.objects.get_for_model(Status).pk],
|
|
1361
|
+
verbose=True,
|
|
1362
|
+
)
|
|
1363
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1364
|
+
log_warn = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_WARNING)
|
|
1365
|
+
self.assertEqual("No statuses found", log_warn.message)
|
|
1366
|
+
self.assertIsNone(
|
|
1367
|
+
JobLogEntry.objects.filter(
|
|
1368
|
+
job_result=job_result, log_level=LogLevelChoices.LOG_SUCCESS, message="Validated successfully"
|
|
1369
|
+
).first()
|
|
1370
|
+
)
|
|
1371
|
+
|
|
1372
|
+
def test_no_restrict_superuser(self):
|
|
1373
|
+
job_result = create_job_result_and_run_job(
|
|
1374
|
+
self.job_module,
|
|
1375
|
+
self.job_name,
|
|
1376
|
+
content_types=[ContentType.objects.get_for_model(User).pk],
|
|
1377
|
+
verbose=True,
|
|
1378
|
+
)
|
|
1379
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_SUCCESS)
|
|
1380
|
+
|
|
1381
|
+
def test_no_restrict_non_superuser(self):
|
|
1382
|
+
job_result = create_job_result_and_run_job(
|
|
1383
|
+
self.job_module,
|
|
1384
|
+
self.job_name,
|
|
1385
|
+
username=self.user.username, # otherwise run_job_for_testing defaults to a superuser account
|
|
1386
|
+
content_types=[ContentType.objects.get_for_model(User).pk],
|
|
1387
|
+
verbose=True,
|
|
1388
|
+
)
|
|
1389
|
+
self.assertJobResultStatus(job_result, JobResultStatusChoices.STATUS_FAILURE)
|
|
1390
|
+
log_fail = JobLogEntry.objects.get(job_result=job_result, log_level=LogLevelChoices.LOG_FAILURE)
|
|
1391
|
+
self.assertIn("Unable to apply access permissions to users.user", log_fail.message)
|
|
@@ -13,6 +13,7 @@ from unittest import mock, TestCase
|
|
|
13
13
|
|
|
14
14
|
from django import __version__ as django_version
|
|
15
15
|
from django.conf import settings
|
|
16
|
+
from django.test import tag
|
|
16
17
|
|
|
17
18
|
from nautobot import __version__ as nautobot_version
|
|
18
19
|
|
|
@@ -101,6 +102,7 @@ class NautobotServerTestCase(TestCase):
|
|
|
101
102
|
|
|
102
103
|
self.assertNotEqual(secret_key_1, secret_key_2)
|
|
103
104
|
|
|
105
|
+
@tag("example_app")
|
|
104
106
|
def test_settings_processing(self):
|
|
105
107
|
result = subprocess.run(
|
|
106
108
|
["nautobot-server", "--config", settings.SETTINGS_PATH, "print_settings"],
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from django.apps import apps
|
|
1
4
|
from django.test import tag, TestCase
|
|
2
5
|
from django.urls import resolve
|
|
3
6
|
|
|
7
|
+
from nautobot.core.apps import NavMenuTab
|
|
4
8
|
from nautobot.core.choices import ButtonActionColorChoices, ButtonActionIconChoices
|
|
5
9
|
from nautobot.core.testing.utils import get_expected_menu_item_name
|
|
10
|
+
from nautobot.core.ui.choices import NavigationIconChoices, NavigationWeightChoices
|
|
6
11
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
12
|
+
from nautobot.core.utils.module_loading import import_string_optional
|
|
7
13
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
8
14
|
from nautobot.extras.registry import registry
|
|
9
15
|
|
|
@@ -45,7 +51,8 @@ class NavMenuTestCase(TestCase):
|
|
|
45
51
|
except AttributeError:
|
|
46
52
|
# Not a model view?
|
|
47
53
|
self.assertIn(
|
|
48
|
-
item_details["name"],
|
|
54
|
+
item_details["name"],
|
|
55
|
+
{"Apps Marketplace", "Installed Apps", "Interface Connections", "Device Constraints"},
|
|
49
56
|
)
|
|
50
57
|
|
|
51
58
|
for button, button_details in item_details["buttons"].items():
|
|
@@ -81,3 +88,71 @@ class NavMenuTestCase(TestCase):
|
|
|
81
88
|
else:
|
|
82
89
|
expected_perms[tab_name] |= group_perms
|
|
83
90
|
self.assertEqual(expected_perms[tab_name], tab_details["permissions"])
|
|
91
|
+
|
|
92
|
+
def test_nav_menu_tabs_have_icon_and_weight(self):
|
|
93
|
+
"""Ensure each NavMenuTab in every navigation.py has an icon and weight set, and any duplicates by name match."""
|
|
94
|
+
tabs_by_name = {}
|
|
95
|
+
for app in apps.get_app_configs():
|
|
96
|
+
if not app.name.startswith("nautobot."):
|
|
97
|
+
continue
|
|
98
|
+
nav_path = f"{app.name}.navigation.menu_items"
|
|
99
|
+
menu_items = import_string_optional(nav_path)
|
|
100
|
+
if menu_items is None:
|
|
101
|
+
continue
|
|
102
|
+
for tab in menu_items:
|
|
103
|
+
if not isinstance(tab, NavMenuTab):
|
|
104
|
+
raise TypeError(f"Expected NavMenuTab instance in {nav_path}, got {type(tab)}")
|
|
105
|
+
tab_name = tab.name
|
|
106
|
+
icon = tab.icon
|
|
107
|
+
weight = tab.weight
|
|
108
|
+
with self.subTest(tab_name=tab_name, nav_path=nav_path):
|
|
109
|
+
self.assertIsNotNone(tab_name, f"Tab in {nav_path} missing 'name'")
|
|
110
|
+
self.assertIsNotNone(icon, f"Tab '{tab_name}' in {nav_path} missing 'icon'")
|
|
111
|
+
self.assertIsNotNone(weight, f"Tab '{tab_name}' in {nav_path} missing 'weight'")
|
|
112
|
+
if tab_name in tabs_by_name:
|
|
113
|
+
prev_icon, prev_weight, prev_path = tabs_by_name[tab_name]
|
|
114
|
+
self.assertEqual(
|
|
115
|
+
icon,
|
|
116
|
+
prev_icon,
|
|
117
|
+
f"Tab '{tab_name}' has inconsistent icons: '{icon}' in {nav_path} vs '{prev_icon}' in {prev_path}",
|
|
118
|
+
)
|
|
119
|
+
self.assertEqual(
|
|
120
|
+
weight,
|
|
121
|
+
prev_weight,
|
|
122
|
+
f"Tab '{tab_name}' has inconsistent weights: '{weight}' in {nav_path} vs '{prev_weight}' in {prev_path}",
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
tabs_by_name[tab_name] = (icon, weight, nav_path)
|
|
126
|
+
|
|
127
|
+
def test_icon_and_weight_class_attributes_match(self):
|
|
128
|
+
"""
|
|
129
|
+
Ensure every class attribute in NavigationIconChoices is also in NavigationWeightChoices and vice versa.
|
|
130
|
+
If not, print the missing/extra attributes for easier debugging.
|
|
131
|
+
"""
|
|
132
|
+
icon_attrs = {attr for attr in dir(NavigationIconChoices) if attr.isupper()}
|
|
133
|
+
weight_attrs = {attr for attr in dir(NavigationWeightChoices) if attr.isupper()}
|
|
134
|
+
|
|
135
|
+
only_in_icons = sorted(icon_attrs - weight_attrs)
|
|
136
|
+
only_in_weights = sorted(weight_attrs - icon_attrs)
|
|
137
|
+
|
|
138
|
+
if only_in_icons or only_in_weights:
|
|
139
|
+
msg = []
|
|
140
|
+
if only_in_icons:
|
|
141
|
+
msg.append(f"Class attributes only in NavigationIconChoices: {only_in_icons}")
|
|
142
|
+
if only_in_weights:
|
|
143
|
+
msg.append(f"Class attributes only in NavigationWeightChoices: {only_in_weights}")
|
|
144
|
+
self.fail("\n".join(msg))
|
|
145
|
+
|
|
146
|
+
def test_navigation_icons_have_svg(self):
|
|
147
|
+
"""Ensure every NavigationIconChoices icon has a corresponding SVG file."""
|
|
148
|
+
missing = []
|
|
149
|
+
svg_dir = os.path.abspath(
|
|
150
|
+
os.path.join(os.path.dirname(__file__), "..", "..", "project-static", "nautobot-icons")
|
|
151
|
+
)
|
|
152
|
+
icon_attrs = [attr for attr in dir(NavigationIconChoices) if attr.isupper() and not attr == "CHOICES"]
|
|
153
|
+
for icon_attr in icon_attrs:
|
|
154
|
+
icon_name = getattr(NavigationIconChoices, icon_attr)
|
|
155
|
+
svg_path = os.path.join(svg_dir, f"{icon_name}.svg")
|
|
156
|
+
if not os.path.isfile(svg_path):
|
|
157
|
+
missing.append(svg_path)
|
|
158
|
+
self.assertFalse(missing, f"Missing SVG files for NavigationIconChoices: {missing}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for social_django storage patch.
|
|
3
|
+
|
|
4
|
+
This tests that the monkeypatch correctly replaces the vulnerable create_user
|
|
5
|
+
method with the secure version that raises AuthAlreadyAssociated instead of
|
|
6
|
+
silently returning an existing user.
|
|
7
|
+
|
|
8
|
+
Please see nautobot/core/utils/patch_social_django.py for details on the patch.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from unittest.mock import MagicMock, patch
|
|
12
|
+
|
|
13
|
+
from nautobot.core.testing import TestCase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PatchSocialDjangoTestCase(TestCase):
|
|
17
|
+
def test_django_storage_has_patch_at_import_time(self):
|
|
18
|
+
"""
|
|
19
|
+
Test that importing DjangoStorage gives us the patched version.
|
|
20
|
+
|
|
21
|
+
This verifies that the patch applied in CoreConfig.ready() persists
|
|
22
|
+
and affects all imports of DjangoStorage throughout the application.
|
|
23
|
+
"""
|
|
24
|
+
from django.db.utils import IntegrityError
|
|
25
|
+
from social_core.exceptions import AuthAlreadyAssociated
|
|
26
|
+
from social_django.models import DjangoStorage
|
|
27
|
+
|
|
28
|
+
# Mock user model to trigger IntegrityError
|
|
29
|
+
mock_user_model = MagicMock()
|
|
30
|
+
mock_manager = MagicMock()
|
|
31
|
+
mock_manager.create_user.side_effect = IntegrityError("duplicate key")
|
|
32
|
+
mock_user_model._default_manager = mock_manager
|
|
33
|
+
|
|
34
|
+
# Patch username_field and user_model methods to return our mock user model
|
|
35
|
+
with patch.object(DjangoStorage.user, "username_field", return_value="username"):
|
|
36
|
+
with patch.object(DjangoStorage.user, "user_model", return_value=mock_user_model):
|
|
37
|
+
# Should raise AuthAlreadyAssociated (patched behavior)
|
|
38
|
+
with self.assertRaises(AuthAlreadyAssociated):
|
|
39
|
+
DjangoStorage.user.create_user(username="test", email="test@example.com")
|
|
40
|
+
|
|
41
|
+
# Verify vulnerable get() not called
|
|
42
|
+
mock_manager.get.assert_not_called()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
from django.test import override_settings, RequestFactory
|
|
4
|
+
from django.urls import reverse
|
|
5
|
+
from rest_framework.response import Response
|
|
6
|
+
|
|
7
|
+
from nautobot.cloud.views import CloudResourceTypeUIViewSet
|
|
8
|
+
from nautobot.core.testing import TestCase
|
|
9
|
+
from nautobot.core.ui.titles import Titles
|
|
10
|
+
from nautobot.core.views.renderers import NautobotHTMLRenderer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ObjectListViewTitlesTest(TestCase):
|
|
14
|
+
"""
|
|
15
|
+
Test suite for verifying that Titles are correctly set in ObjectListView rendering.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
user_permissions = ["cloud.view_cloudresourcetype"]
|
|
19
|
+
|
|
20
|
+
def setUp(self):
|
|
21
|
+
super().setUp()
|
|
22
|
+
self.factory = RequestFactory()
|
|
23
|
+
|
|
24
|
+
@override_settings(ALLOWED_HOSTS=["*"], PAGINATE_COUNT=5, MAX_PAGE_SIZE=10)
|
|
25
|
+
def test_uiviewset_list_view_title(self):
|
|
26
|
+
"""
|
|
27
|
+
Test that the list view title is correctly set from the Titles configuration.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
path = reverse("cloud:cloudresourcetype_list")
|
|
31
|
+
request = self.factory.get(path)
|
|
32
|
+
request.user = self.user
|
|
33
|
+
viewset_class = CloudResourceTypeUIViewSet
|
|
34
|
+
with patch.object(CloudResourceTypeUIViewSet, "view_titles", Titles(titles={"list": "Burritos"})):
|
|
35
|
+
view = viewset_class()
|
|
36
|
+
view.action_map = {"get": "list"}
|
|
37
|
+
|
|
38
|
+
request = view.initialize_request(request)
|
|
39
|
+
|
|
40
|
+
view.setup(request)
|
|
41
|
+
view.initial(request)
|
|
42
|
+
|
|
43
|
+
renderer = NautobotHTMLRenderer()
|
|
44
|
+
context = renderer.get_context(
|
|
45
|
+
data={},
|
|
46
|
+
accepted_media_type="text/html",
|
|
47
|
+
renderer_context={"view": view, "request": request, "response": Response({})},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Verify that the title is set in the context
|
|
51
|
+
self.assertIn("view_titles", context)
|
|
52
|
+
self.assertNotIn(
|
|
53
|
+
"title", context
|
|
54
|
+
) # title is used within the render path but should not be directly in context
|
|
55
|
+
self.assertEqual(context["view_titles"], viewset_class.view_titles)
|
|
56
|
+
|
|
57
|
+
# Finally, render the view and verify the title appears in the response
|
|
58
|
+
response = self.client.get(path)
|
|
59
|
+
self.assertContains(response, "Burritos")
|
|
@@ -132,6 +132,7 @@ class SettingsJSONSchemaTestCase(TestCase):
|
|
|
132
132
|
"CSRF_FAILURE_VIEW",
|
|
133
133
|
"DATA_UPLOAD_MAX_NUMBER_FIELDS",
|
|
134
134
|
"DEFAULT_AUTO_FIELD",
|
|
135
|
+
"DJANGO_TABLES2_TEMPLATE",
|
|
135
136
|
"DRF_REACT_TEMPLATE_TYPE_MAP",
|
|
136
137
|
"EXEMPT_EXCLUDE_MODELS",
|
|
137
138
|
"FILTERS_NULL_CHOICE_LABEL",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from django.test import TestCase
|
|
1
|
+
from django.test import tag, TestCase
|
|
2
2
|
|
|
3
3
|
from nautobot.circuits.models import Circuit
|
|
4
4
|
from nautobot.circuits.tables import CircuitTable
|
|
@@ -32,6 +32,7 @@ class TableTestCase(TestCase):
|
|
|
32
32
|
)
|
|
33
33
|
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
34
34
|
|
|
35
|
+
@tag("example_app")
|
|
35
36
|
def test_tree_model_table_orderable(self):
|
|
36
37
|
"""Assert TreeNode model table are orderable."""
|
|
37
38
|
location_type = LocationType.objects.get(name="Campus")
|
|
@@ -107,6 +108,7 @@ class TableTestCase(TestCase):
|
|
|
107
108
|
)
|
|
108
109
|
self.assertEqual(list(table_queryset_data), list(sorted_queryset))
|
|
109
110
|
|
|
111
|
+
@tag("example_app")
|
|
110
112
|
def test_base_table_apis(self):
|
|
111
113
|
"""
|
|
112
114
|
Test BaseTable APIs, specifically visible_columns and configurable_columns.
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
from unittest import mock
|
|
2
|
+
|
|
1
3
|
from constance.test import override_config
|
|
2
4
|
from django.conf import settings
|
|
5
|
+
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
|
3
6
|
from django.templatetags.static import static
|
|
4
|
-
from django.test import override_settings,
|
|
7
|
+
from django.test import override_settings, tag
|
|
5
8
|
|
|
6
9
|
from nautobot.core.templatetags import helpers
|
|
10
|
+
from nautobot.core.testing import TestCase
|
|
7
11
|
from nautobot.dcim import models
|
|
8
12
|
from nautobot.ipam.models import VLAN
|
|
9
13
|
|
|
10
|
-
from example_app.models import AnotherExampleModel, ExampleModel
|
|
11
|
-
|
|
12
14
|
|
|
13
15
|
class NautobotTemplatetagsHelperTest(TestCase):
|
|
14
16
|
def test_hyperlinked_object(self):
|
|
@@ -61,6 +63,9 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
61
63
|
self.assertEqual(helpers.pre_tag(None), '<span class="text-secondary">—</span>')
|
|
62
64
|
self.assertEqual(helpers.pre_tag([]), "<pre>[]</pre>")
|
|
63
65
|
self.assertEqual(helpers.pre_tag("something"), "<pre>something</pre>")
|
|
66
|
+
self.assertEqual(helpers.pre_tag("", format_empty_value=False), '<span class="text-secondary">—</span>')
|
|
67
|
+
self.assertEqual(helpers.pre_tag([], format_empty_value=False), '<span class="text-secondary">—</span>')
|
|
68
|
+
self.assertEqual(helpers.pre_tag("something", format_empty_value=False), "<pre>something</pre>")
|
|
64
69
|
|
|
65
70
|
def test_add_html_id(self):
|
|
66
71
|
# Case where what we have isn't actually a HTML element but just a bare string
|
|
@@ -132,6 +137,7 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
132
137
|
)
|
|
133
138
|
self.assertEqual("utf8:\n- 😀😀\n- 😀\n", helpers.render_yaml({"utf8": ["😀😀", "😀"]}, False))
|
|
134
139
|
|
|
140
|
+
@tag("example_app")
|
|
135
141
|
def test_meta(self):
|
|
136
142
|
location = models.Location.objects.first()
|
|
137
143
|
|
|
@@ -139,31 +145,42 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
139
145
|
self.assertEqual(helpers.meta(models.Location, "app_label"), "dcim")
|
|
140
146
|
self.assertEqual(helpers.meta(location, "not_present"), "")
|
|
141
147
|
|
|
148
|
+
from example_app.models import ExampleModel
|
|
149
|
+
|
|
142
150
|
self.assertEqual(helpers.meta(ExampleModel, "app_label"), "example_app")
|
|
143
151
|
|
|
152
|
+
@tag("example_app")
|
|
144
153
|
def test_viewname(self):
|
|
145
154
|
location = models.Location.objects.first()
|
|
146
155
|
|
|
147
156
|
self.assertEqual(helpers.viewname(location, "edit"), "dcim:location_edit")
|
|
148
157
|
self.assertEqual(helpers.viewname(models.Location, "test"), "dcim:location_test")
|
|
149
158
|
|
|
159
|
+
from example_app.models import ExampleModel
|
|
160
|
+
|
|
150
161
|
self.assertEqual(helpers.viewname(ExampleModel, "edit"), "plugins:example_app:examplemodel_edit")
|
|
151
162
|
|
|
163
|
+
@tag("example_app")
|
|
152
164
|
def test_validated_viewname(self):
|
|
153
165
|
location = models.Location.objects.first()
|
|
154
166
|
|
|
155
167
|
self.assertEqual(helpers.validated_viewname(location, "list"), "dcim:location_list")
|
|
156
168
|
self.assertIsNone(helpers.validated_viewname(models.Location, "notvalid"))
|
|
157
169
|
|
|
170
|
+
from example_app.models import ExampleModel
|
|
171
|
+
|
|
158
172
|
self.assertEqual(helpers.validated_viewname(ExampleModel, "list"), "plugins:example_app:examplemodel_list")
|
|
159
173
|
self.assertIsNone(helpers.validated_viewname(ExampleModel, "notvalid"))
|
|
160
174
|
|
|
175
|
+
@tag("example_app")
|
|
161
176
|
def test_validated_api_viewname(self):
|
|
162
177
|
location = models.Location.objects.first()
|
|
163
178
|
|
|
164
179
|
self.assertEqual(helpers.validated_api_viewname(location, "list"), "dcim-api:location-list")
|
|
165
180
|
self.assertIsNone(helpers.validated_api_viewname(models.Location, "notvalid"))
|
|
166
181
|
|
|
182
|
+
from example_app.models import ExampleModel
|
|
183
|
+
|
|
167
184
|
self.assertEqual(
|
|
168
185
|
helpers.validated_api_viewname(ExampleModel, "list"), "plugins-api:example_app-api:examplemodel-list"
|
|
169
186
|
)
|
|
@@ -180,7 +197,13 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
180
197
|
def test_humanize_speed(self):
|
|
181
198
|
self.assertEqual(helpers.humanize_speed(1544), "1.544 Mbps")
|
|
182
199
|
self.assertEqual(helpers.humanize_speed(100000), "100 Mbps")
|
|
200
|
+
self.assertEqual(helpers.humanize_speed(2500000), "2.5 Gbps")
|
|
183
201
|
self.assertEqual(helpers.humanize_speed(10000000), "10 Gbps")
|
|
202
|
+
self.assertEqual(helpers.humanize_speed(100000000), "100 Gbps")
|
|
203
|
+
self.assertEqual(helpers.humanize_speed(1000000000), "1 Tbps")
|
|
204
|
+
self.assertEqual(helpers.humanize_speed(1600000000), "1.6 Tbps")
|
|
205
|
+
self.assertEqual(helpers.humanize_speed(10000000000), "10 Tbps")
|
|
206
|
+
self.assertEqual(helpers.humanize_speed(100000000000), "100 Tbps")
|
|
184
207
|
|
|
185
208
|
def test_tzoffset(self):
|
|
186
209
|
self.assertTrue(callable(helpers.tzoffset))
|
|
@@ -201,16 +224,6 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
201
224
|
self.assertEqual(helpers.percentage(2, 10), 20)
|
|
202
225
|
self.assertEqual(helpers.percentage(10, 3), 333)
|
|
203
226
|
|
|
204
|
-
def test_get_docs_url(self):
|
|
205
|
-
self.assertTrue(callable(helpers.get_docs_url))
|
|
206
|
-
location = models.Location.objects.first()
|
|
207
|
-
self.assertEqual(helpers.get_docs_url(location), static("docs/user-guide/core-data-model/dcim/location.html"))
|
|
208
|
-
example_model = ExampleModel.objects.create(name="test", number=1)
|
|
209
|
-
self.assertEqual(helpers.get_docs_url(example_model), static("example_app/docs/models/examplemodel.html"))
|
|
210
|
-
# AnotherExampleModel does not have documentation.
|
|
211
|
-
another_model = AnotherExampleModel.objects.create(name="test", number=1)
|
|
212
|
-
self.assertIsNone(helpers.get_docs_url(another_model))
|
|
213
|
-
|
|
214
227
|
def test_has_perms(self):
|
|
215
228
|
self.assertTrue(callable(helpers.has_perms))
|
|
216
229
|
# TODO add unit tests for has_perms
|
|
@@ -265,6 +278,7 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
265
278
|
# Assert when obj is None
|
|
266
279
|
self.assertEqual(helpers.hyperlinked_object_with_color(obj=None), '<span class="text-secondary">—</span>')
|
|
267
280
|
|
|
281
|
+
@tag("example_app")
|
|
268
282
|
@override_settings(BANNER_TOP="¡Hola, mundo!")
|
|
269
283
|
@override_config(example_app__SAMPLE_VARIABLE="Testing")
|
|
270
284
|
def test_settings_or_config(self):
|
|
@@ -338,3 +352,38 @@ class NautobotTemplatetagsHelperTest(TestCase):
|
|
|
338
352
|
"-85 dBm",
|
|
339
353
|
)
|
|
340
354
|
self.assertEqual(helpers.dbm(None), helpers.placeholder(None))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@tag("test")
|
|
358
|
+
class NautobotStaticDocsTestCase(StaticLiveServerTestCase):
|
|
359
|
+
@tag("example_app")
|
|
360
|
+
def test_get_docs_url(self):
|
|
361
|
+
self.assertTrue(callable(helpers.get_docs_url))
|
|
362
|
+
location_type = models.LocationType.objects.create(name="Some Location Type")
|
|
363
|
+
self.assertEqual(
|
|
364
|
+
helpers.get_docs_url(location_type), static("docs/user-guide/core-data-model/dcim/locationtype.html")
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
from example_app.models import AnotherExampleModel, ExampleModel
|
|
368
|
+
|
|
369
|
+
example_model = ExampleModel.objects.create(name="test", number=1)
|
|
370
|
+
self.assertEqual(helpers.get_docs_url(example_model), "/docs/example-app/models/examplemodel.html")
|
|
371
|
+
# AnotherExampleModel does not have documentation.
|
|
372
|
+
another_model = AnotherExampleModel.objects.create(name="test", number=1)
|
|
373
|
+
self.assertIsNone(helpers.get_docs_url(another_model))
|
|
374
|
+
|
|
375
|
+
@tag("example_app")
|
|
376
|
+
@mock.patch("nautobot.core.templatetags.helpers.find", return_value=False)
|
|
377
|
+
@mock.patch("nautobot.core.templatetags.helpers.resources.files", side_effect=ModuleNotFoundError)
|
|
378
|
+
def test_get_docs_url_module_not_found_and_no_static_file(self, mock_files, mock_find):
|
|
379
|
+
# Force `resources.files()` to raise ModuleNotFoundError to simulate a plugin
|
|
380
|
+
# that is listed in settings.PLUGINS but doesn't actually exist on disk.
|
|
381
|
+
# This ensures the `except ModuleNotFoundError` branch is covered.
|
|
382
|
+
from example_app.models import ExampleModel
|
|
383
|
+
|
|
384
|
+
example_model = ExampleModel.objects.create(name="test", number=1)
|
|
385
|
+
result = helpers.get_docs_url(example_model)
|
|
386
|
+
self.assertIsNone(result)
|
|
387
|
+
|
|
388
|
+
mock_files.assert_called_once()
|
|
389
|
+
mock_find.assert_called_once()
|
|
@@ -65,7 +65,7 @@ class NautobotTemplatetagsUIComponentsTest(TestCase):
|
|
|
65
65
|
{
|
|
66
66
|
"list_url": "home",
|
|
67
67
|
"title": "New Home",
|
|
68
|
-
"
|
|
68
|
+
"detail": True,
|
|
69
69
|
"breadcrumbs": Breadcrumbs(),
|
|
70
70
|
}
|
|
71
71
|
)
|
|
@@ -98,7 +98,7 @@ class NautobotTemplatetagsUIComponentsTest(TestCase):
|
|
|
98
98
|
{
|
|
99
99
|
"list_url": "home",
|
|
100
100
|
"title": "New Home",
|
|
101
|
-
"
|
|
101
|
+
"detail": True,
|
|
102
102
|
"breadcrumbs": Breadcrumbs(),
|
|
103
103
|
}
|
|
104
104
|
)
|
|
@@ -126,7 +126,7 @@ class NautobotTemplatetagsUIComponentsTest(TestCase):
|
|
|
126
126
|
{
|
|
127
127
|
"list_url": "home",
|
|
128
128
|
"title": "New Home",
|
|
129
|
-
"
|
|
129
|
+
"detail": True,
|
|
130
130
|
"breadcrumbs": Breadcrumbs(),
|
|
131
131
|
}
|
|
132
132
|
)
|
|
@@ -152,7 +152,7 @@ class NautobotTemplatetagsUIComponentsTest(TestCase):
|
|
|
152
152
|
{
|
|
153
153
|
"list_url": "home",
|
|
154
154
|
"title": "New Home",
|
|
155
|
-
"
|
|
155
|
+
"detail": True,
|
|
156
156
|
"breadcrumbs": Breadcrumbs(),
|
|
157
157
|
}
|
|
158
158
|
)
|
|
@@ -107,22 +107,6 @@ class TitlesTestCase(TestCase):
|
|
|
107
107
|
"context": {"view_action": "bulk_update", "objs_count": 10, "verbose_name_plural": "devices"},
|
|
108
108
|
"expected": "Editing 10 Devices",
|
|
109
109
|
},
|
|
110
|
-
{
|
|
111
|
-
"name": "changelog_action",
|
|
112
|
-
"context": {
|
|
113
|
-
"view_action": "changelog",
|
|
114
|
-
"object": location_type,
|
|
115
|
-
},
|
|
116
|
-
"expected": "Test Location Type Title - Change Log",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"name": "notes_action",
|
|
120
|
-
"context": {
|
|
121
|
-
"view_action": "notes",
|
|
122
|
-
"object": location_type,
|
|
123
|
-
},
|
|
124
|
-
"expected": "Test Location Type Title - Notes",
|
|
125
|
-
},
|
|
126
110
|
{
|
|
127
111
|
"name": "approve_action",
|
|
128
112
|
"context": {"view_action": "approve", "verbose_name": "device"},
|
|
@@ -13,8 +13,21 @@ class TestInvalidateMaxTreeDepthSignal(TestCase):
|
|
|
13
13
|
# Ensure that the max_depth hasn't already been cached
|
|
14
14
|
Location.objects.__dict__.pop("max_depth", None)
|
|
15
15
|
location = Location.objects.first()
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
with CaptureQueriesContext(connection) as ctx:
|
|
17
18
|
location.save()
|
|
19
|
+
captured_tree_cte_queries = [
|
|
20
|
+
query["sql"] for query in ctx.captured_queries if "WITH RECURSIVE" in query["sql"]
|
|
21
|
+
]
|
|
22
|
+
allowed_number_of_tree_queries = 0 # We don't expect any tree queries to be run
|
|
23
|
+
_query_separator = "\n" + ("-" * 10) + "\n" + "NEXT QUERY" + "\n" + ("-" * 10)
|
|
24
|
+
self.assertEqual(
|
|
25
|
+
len(captured_tree_cte_queries),
|
|
26
|
+
allowed_number_of_tree_queries,
|
|
27
|
+
f"The CTE tree was calculated a different number of times ({len(captured_tree_cte_queries)})"
|
|
28
|
+
f" than allowed ({allowed_number_of_tree_queries})."
|
|
29
|
+
f" The following queries were used:\n{_query_separator.join(captured_tree_cte_queries)}",
|
|
30
|
+
)
|
|
18
31
|
|
|
19
32
|
|
|
20
33
|
class QuerySetAncestorTests(TestCase):
|