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
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
</div>
|
|
76
76
|
|
|
77
77
|
<div class="d-print-none justify-content-between mb-n20 nb-form-sticky-footer">
|
|
78
|
-
<button type="reset" class="btn btn-secondary">
|
|
79
|
-
<span aria-hidden="true" class="mdi mdi-close me-4"></span><!--
|
|
80
|
-
-->Clear All
|
|
81
|
-
</button>
|
|
82
78
|
<button type="submit" class="btn btn-primary">
|
|
83
79
|
<span class="mdi mdi-check me-4" aria-hidden="true"></span><!--
|
|
84
80
|
-->Apply Specified
|
|
85
81
|
</button>
|
|
82
|
+
<button type="reset" class="btn btn-secondary">
|
|
83
|
+
<span aria-hidden="true" class="mdi mdi-close me-4"></span><!--
|
|
84
|
+
-->Clear All
|
|
85
|
+
</button>
|
|
86
86
|
</div>
|
|
87
87
|
</form>
|
|
88
88
|
</div>
|
|
@@ -124,6 +124,31 @@
|
|
|
124
124
|
dynamicFilterForm: document.querySelector('#dynamic-filter-form'),
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
const prepopulateFilterSelectsFromURL = (() => {
|
|
128
|
+
const { defaultFilterForm } = getFilterForms();
|
|
129
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
130
|
+
// Only target Select2 controls inside the default filter form.
|
|
131
|
+
[...defaultFilterForm.querySelectorAll('select.nautobot-select2-multi-value-char')].forEach((el) => {
|
|
132
|
+
const name = el.getAttribute('name');
|
|
133
|
+
if (!name) { return; }
|
|
134
|
+
const values = urlParams.getAll(name);
|
|
135
|
+
if (!values.length) { return; }
|
|
136
|
+
values.forEach((val) => {
|
|
137
|
+
let found = Array.prototype.find.call(el.options, (opt) => {
|
|
138
|
+
return String(opt.value) === String(val);
|
|
139
|
+
});
|
|
140
|
+
if (found) {
|
|
141
|
+
found.selected = true;
|
|
142
|
+
} else {
|
|
143
|
+
el.add(new Option(val, val, true, true));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
if (window.jQuery && $(el).data('select2')) {
|
|
147
|
+
$(el).trigger('change');
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
})();
|
|
151
|
+
|
|
127
152
|
/**
|
|
128
153
|
* Synchronize given `filter` value from default to dynamic filter form.
|
|
129
154
|
* @param {string} name - Filter name.
|
|
@@ -435,6 +460,14 @@
|
|
|
435
460
|
|
|
436
461
|
const { defaultFilterForm, dynamicFilterForm } = getFilterForms();
|
|
437
462
|
|
|
463
|
+
/*
|
|
464
|
+
* Just before the form submission automatically add filter selected in "Advanced" tab (if any)
|
|
465
|
+
* which hasn't been manually applied for whatever reason (usually forgetfulness, speed, etc.).
|
|
466
|
+
* This is a requested UX flavor.
|
|
467
|
+
*/
|
|
468
|
+
const add = dynamicFilterForm.querySelector('button.nb-dynamic-filter-add');
|
|
469
|
+
add.click();
|
|
470
|
+
|
|
438
471
|
// Remove all field names which are not applied filters container descendants.
|
|
439
472
|
[...dynamicFilterForm.querySelectorAll('input, select, textarea')].forEach((field) =>
|
|
440
473
|
field.closest('.nb-dynamic-filter-items') ? undefined : field.removeAttribute('name'),
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
{% export_button content_type list_element=True %}
|
|
57
57
|
</ul>
|
|
58
58
|
</div>
|
|
59
|
-
<div class="
|
|
59
|
+
<div class="dropdown d-inline-flex">
|
|
60
60
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
|
61
61
|
Actions
|
|
62
62
|
</button>
|
|
@@ -234,11 +234,11 @@
|
|
|
234
234
|
<div class="mb-10 d-flex justify-content-center">
|
|
235
235
|
<label class="col-md-3 col-form-label nb-required" for="id_status">
|
|
236
236
|
Status
|
|
237
|
-
<span class="form-text">Help text for this form field</span>
|
|
238
237
|
</label>
|
|
239
238
|
<div class="col-md-9">
|
|
240
239
|
<select name="status" class="nautobot-select2-api form-control select2-hidden-accessible" data-query-param-content_types="["dcim.device"]" display-field="display" value-field="id" data-depth="0" required="" placeholder="Status" data-url="/api/extras/statuses/" id="id_status">
|
|
241
240
|
</select>
|
|
241
|
+
<span class="form-text">Help text for this form field</span>
|
|
242
242
|
</div>
|
|
243
243
|
</div>
|
|
244
244
|
</div>
|
|
@@ -471,6 +471,15 @@
|
|
|
471
471
|
<button type="button" class="btn btn-link disabled">Link</button>
|
|
472
472
|
</td>
|
|
473
473
|
</tr>
|
|
474
|
+
<tr>
|
|
475
|
+
<td><code>btn-group</code> like radio buttons</td>
|
|
476
|
+
<td>
|
|
477
|
+
<div class="btn-group" role="group">
|
|
478
|
+
<button type="button" class="btn btn-primary bg-primary nb-text-body-bg">Current Active Option</button>
|
|
479
|
+
<button type="button" class="btn btn-primary">Alternative Option</button>
|
|
480
|
+
</div>
|
|
481
|
+
</td>
|
|
482
|
+
</tr>
|
|
474
483
|
</table>
|
|
475
484
|
</div>
|
|
476
485
|
</div>
|
|
@@ -1120,15 +1129,19 @@ This:
|
|
|
1120
1129
|
<h1>Nautobot Icons</h1>
|
|
1121
1130
|
<div class="row">
|
|
1122
1131
|
<div class="col-12">
|
|
1123
|
-
<div class="column-gap-8 d-flex flex-wrap px-20 py-16 rounded row-gap-16" style="background-color: #
|
|
1132
|
+
<div class="column-gap-8 d-flex flex-wrap px-20 py-16 rounded row-gap-16" style="background-color: #121212;">
|
|
1124
1133
|
<div class="icon-preview"><img alt="360-degrees icon" src="{% static 'nautobot-icons/360-degrees.svg' %}">360-degrees</div>
|
|
1125
1134
|
<div class="icon-preview"><img alt="arrow-decision icon" src="{% static 'nautobot-icons/arrow-decision.svg' %}">arrow-decision</div>
|
|
1126
1135
|
<div class="icon-preview"><img alt="arrows-expand-rec icon" src="{% static 'nautobot-icons/arrows-expand-rec.svg' %}">arrows-expand-rec</div>
|
|
1127
1136
|
<div class="icon-preview"><img alt="arrows-move-rec icon" src="{% static 'nautobot-icons/arrows-move-rec.svg' %}">arrows-move-rec</div>
|
|
1128
1137
|
<div class="icon-preview"><img alt="arrows-move-2-rec icon" src="{% static 'nautobot-icons/arrows-move-2-rec.svg' %}">arrows-move-2-rec</div>
|
|
1129
1138
|
<div class="icon-preview"><img alt="atom icon" src="{% static 'nautobot-icons/atom.svg' %}">atom</div>
|
|
1139
|
+
<div class="icon-preview"><img alt="power icon" src="{% static 'nautobot-icons/battery-3.svg' %}">battery-3</div>
|
|
1130
1140
|
<div class="icon-preview"><img alt="branch icon" src="{% static 'nautobot-icons/branch.svg' %}">branch</div>
|
|
1131
1141
|
<div class="icon-preview"><img alt="briefcase-2 icon" src="{% static 'nautobot-icons/briefcase-2.svg' %}">briefcase-2</div>
|
|
1142
|
+
<div class="icon-preview"><img alt="bus-globe icon" src="{% static 'nautobot-icons/bus-globe.svg' %}">bus-globe</div>
|
|
1143
|
+
<div class="icon-preview"><img alt="bus-shield icon" src="{% static 'nautobot-icons/bus-shield.svg' %}">bus-shield</div>
|
|
1144
|
+
<div class="icon-preview"><img alt="bus-shield-check icon" src="{% static 'nautobot-icons/bus-shield-check.svg' %}">bus-shield-check</div>
|
|
1132
1145
|
<div class="icon-preview"><img alt="cable-data icon" src="{% static 'nautobot-icons/cable-data.svg' %}">cable-data</div>
|
|
1133
1146
|
<div class="icon-preview"><img alt="cable-data-2 icon" src="{% static 'nautobot-icons/cable-data-2.svg' %}">cable-data-2</div>
|
|
1134
1147
|
<div class="icon-preview"><img alt="cast icon" src="{% static 'nautobot-icons/cast.svg' %}">cast</div>
|
|
@@ -1145,6 +1158,7 @@ This:
|
|
|
1145
1158
|
<div class="icon-preview"><img alt="device-lifecycle icon" src="{% static 'nautobot-icons/device-lifecycle.svg' %}">device-lifecycle</div>
|
|
1146
1159
|
<div class="icon-preview"><img alt="direction icon" src="{% static 'nautobot-icons/direction.svg' %}">direction</div>
|
|
1147
1160
|
<div class="icon-preview"><img alt="elements icon" src="{% static 'nautobot-icons/elements.svg' %}">elements</div>
|
|
1161
|
+
<div class="icon-preview"><img alt="extensibility icon" src="{% static 'nautobot-icons/extensibility.svg' %}">extensibility</div>
|
|
1148
1162
|
<div class="icon-preview"><img alt="globe icon" src="{% static 'nautobot-icons/globe.svg' %}">globe</div>
|
|
1149
1163
|
<div class="icon-preview"><img alt="globe-2 icon" src="{% static 'nautobot-icons/globe-2.svg' %}">globe-2</div>
|
|
1150
1164
|
<div class="icon-preview"><img alt="hammer icon" src="{% static 'nautobot-icons/hammer.svg' %}">hammer</div>
|
|
@@ -1154,6 +1168,7 @@ This:
|
|
|
1154
1168
|
<div class="icon-preview"><img alt="lightning icon" src="{% static 'nautobot-icons/lightning.svg' %}">lightning</div>
|
|
1155
1169
|
<div class="icon-preview"><img alt="list-unordered icon" src="{% static 'nautobot-icons/list-unordered.svg' %}">list-unordered</div>
|
|
1156
1170
|
<div class="icon-preview"><img alt="map-view icon" src="{% static 'nautobot-icons/map-view.svg' %}">map-view</div>
|
|
1171
|
+
<div class="icon-preview"><img alt="organization icon" src="{% static 'nautobot-icons/organization.svg' %}">organization</div>
|
|
1157
1172
|
<div class="icon-preview"><img alt="pin-2 icon" src="{% static 'nautobot-icons/pin-2.svg' %}">pin-2</div>
|
|
1158
1173
|
<div class="icon-preview"><img alt="pin-3 icon" src="{% static 'nautobot-icons/pin-3.svg' %}">pin-3</div>
|
|
1159
1174
|
<div class="icon-preview"><img alt="plug icon" src="{% static 'nautobot-icons/plug.svg' %}">plug</div>
|
|
@@ -1162,6 +1177,7 @@ This:
|
|
|
1162
1177
|
<div class="icon-preview"><img alt="rotate-cw icon" src="{% static 'nautobot-icons/rotate-cw.svg' %}">rotate-cw</div>
|
|
1163
1178
|
<div class="icon-preview"><img alt="route icon" src="{% static 'nautobot-icons/route.svg' %}">route</div>
|
|
1164
1179
|
<div class="icon-preview"><img alt="secrets icon" src="{% static 'nautobot-icons/secrets.svg' %}">secrets</div>
|
|
1180
|
+
<div class="icon-preview"><img alt="security icon" src="{% static 'nautobot-icons/security.svg' %}">security</div>
|
|
1165
1181
|
<div class="icon-preview"><img alt="server icon" src="{% static 'nautobot-icons/server.svg' %}">server</div>
|
|
1166
1182
|
<div class="icon-preview"><img alt="server-2 icon" src="{% static 'nautobot-icons/server-2.svg' %}">server-2</div>
|
|
1167
1183
|
<div class="icon-preview"><img alt="share icon" src="{% static 'nautobot-icons/share.svg' %}">share</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<div class="input-group">
|
|
2
|
+
{% include 'django/forms/widgets/number.html' %}
|
|
3
|
+
{% if widget.choices %}
|
|
4
|
+
<span class="input-group-btn">
|
|
5
|
+
<button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
6
|
+
<span class="mdi mdi-chevron-down"></span>
|
|
7
|
+
</button>
|
|
8
|
+
<ul class="dropdown-menu dropdown-menu-end">
|
|
9
|
+
{% for value, label in widget.choices %}
|
|
10
|
+
<li><a href="#" data-name="{{ widget.name }}" data-value="{{ value }}" class="set_value dropdown-item">{{ label }}</a></li>
|
|
11
|
+
{% endfor %}
|
|
12
|
+
</ul>
|
|
13
|
+
</span>
|
|
14
|
+
{% endif %}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
{% if widget.choices %}
|
|
18
|
+
<script type="text/javascript">
|
|
19
|
+
(function() {
|
|
20
|
+
if (window.__nbNumberWithSelectWidgetBound) return;
|
|
21
|
+
window.__nbNumberWithSelectWidgetBound = true;
|
|
22
|
+
function bindNumberWithSelectHandler() {
|
|
23
|
+
document.addEventListener("click", function(e) {
|
|
24
|
+
if (!e.target) return;
|
|
25
|
+
var link = e.target.closest && e.target.closest("a.set_value");
|
|
26
|
+
if (!link) return;
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
var container = link.closest(".input-group");
|
|
29
|
+
var name = link.getAttribute("data-name");
|
|
30
|
+
var value = link.getAttribute("data-value");
|
|
31
|
+
var input = container && name ? container.querySelector('input[name="' + name + '"]') : null;
|
|
32
|
+
if (input) {
|
|
33
|
+
input.value = value;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (document.readyState === 'loading') {
|
|
38
|
+
document.addEventListener('DOMContentLoaded', bindNumberWithSelectHandler);
|
|
39
|
+
} else {
|
|
40
|
+
bindNumberWithSelectHandler();
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
</script>
|
|
44
|
+
{% endif %}
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
<option value="{{ widget.value }}"
|
|
1
|
+
<option value="{{ widget.value }}"
|
|
2
|
+
{% include "django/forms/widgets/attrs.html" %}
|
|
3
|
+
{% if widget.label.disabled %} disabled{% endif %}>{{ widget.label.label|default:widget.label }}</option>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
import datetime
|
|
3
|
+
from importlib import resources
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
5
6
|
import re
|
|
@@ -7,6 +8,7 @@ from typing import Literal
|
|
|
7
8
|
from urllib.parse import parse_qs, quote_plus
|
|
8
9
|
|
|
9
10
|
from django import template
|
|
11
|
+
from django.apps import apps
|
|
10
12
|
from django.conf import settings
|
|
11
13
|
from django.contrib import messages
|
|
12
14
|
from django.contrib.auth.models import AnonymousUser
|
|
@@ -14,9 +16,11 @@ from django.contrib.staticfiles.finders import find
|
|
|
14
16
|
from django.core.exceptions import ObjectDoesNotExist
|
|
15
17
|
from django.templatetags.static import static, StaticNode
|
|
16
18
|
from django.urls import NoReverseMatch, reverse
|
|
19
|
+
from django.utils.formats import date_format
|
|
17
20
|
from django.utils.html import format_html, format_html_join, strip_tags
|
|
18
21
|
from django.utils.safestring import mark_safe
|
|
19
22
|
from django.utils.text import slugify as django_slugify
|
|
23
|
+
from django.utils.translation import gettext as _
|
|
20
24
|
from django_jinja import library
|
|
21
25
|
from markdown import markdown
|
|
22
26
|
import yaml
|
|
@@ -122,23 +126,29 @@ def placeholder(value):
|
|
|
122
126
|
|
|
123
127
|
@library.filter()
|
|
124
128
|
@register.filter()
|
|
125
|
-
def pre_tag(value):
|
|
129
|
+
def pre_tag(value, format_empty_value=True):
|
|
126
130
|
"""Render a value within `<pre></pre>` tags to enable formatting.
|
|
127
131
|
|
|
128
132
|
Args:
|
|
129
133
|
value (any): Input value, can be any variable.
|
|
134
|
+
format_empty_value (bool): Whether format empty value or render placeholder.
|
|
130
135
|
|
|
131
136
|
Returns:
|
|
132
|
-
(str): Value wrapped in `<pre></pre>` tags
|
|
137
|
+
(str): Value wrapped in `<pre></pre>` tags or placeholder if None or format_empty_values=False and empty
|
|
133
138
|
|
|
134
139
|
Example:
|
|
135
140
|
>>> pre_tag("")
|
|
136
141
|
'<pre></pre>'
|
|
137
142
|
>>> pre_tag("hello")
|
|
138
143
|
'<pre>hello</pre>'
|
|
144
|
+
>>> pre_tag("", format_empty_value=False)
|
|
145
|
+
'<span class="text-secondary">—</span>'
|
|
139
146
|
"""
|
|
140
|
-
if value is not None:
|
|
147
|
+
if format_empty_value and value is not None:
|
|
148
|
+
return format_html("<pre>{}</pre>", value)
|
|
149
|
+
elif value:
|
|
141
150
|
return format_html("<pre>{}</pre>", value)
|
|
151
|
+
|
|
142
152
|
return HTML_NONE
|
|
143
153
|
|
|
144
154
|
|
|
@@ -391,17 +401,19 @@ def humanize_speed(speed):
|
|
|
391
401
|
1544 => "1.544 Mbps"
|
|
392
402
|
100000 => "100 Mbps"
|
|
393
403
|
10000000 => "10 Gbps"
|
|
404
|
+
1000000000 => "1 Tbps"
|
|
405
|
+
1600000000 => "1.6 Tbps"
|
|
406
|
+
10000000000 => "10 Tbps"
|
|
394
407
|
"""
|
|
395
408
|
if not speed:
|
|
396
409
|
return ""
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return f"{int(speed / 1000)} Mbps"
|
|
410
|
+
|
|
411
|
+
if speed >= 1000000000:
|
|
412
|
+
return f"{speed / 1000000000:g} Tbps"
|
|
413
|
+
elif speed >= 1000000:
|
|
414
|
+
return f"{speed / 1000000:g} Gbps"
|
|
403
415
|
elif speed >= 1000:
|
|
404
|
-
return f"{
|
|
416
|
+
return f"{speed / 1000:g} Mbps"
|
|
405
417
|
else:
|
|
406
418
|
return f"{speed} Kbps"
|
|
407
419
|
|
|
@@ -483,11 +495,13 @@ def percentage(x, y):
|
|
|
483
495
|
@library.filter()
|
|
484
496
|
@register.filter()
|
|
485
497
|
def get_docs_url(model):
|
|
486
|
-
"""Return the
|
|
498
|
+
"""Return the documentation URL for the specified model, if it can be found/predicted.
|
|
487
499
|
|
|
488
500
|
- Core models, as of 2.0, are usually at `docs/user-guide/core-data-model/{app_label}/{model_name}.html`.
|
|
489
501
|
- Models in the `extras` app are usually at `docs/user-guide/platform-functionality/{model_name}.html`.
|
|
490
|
-
- Apps (plugins) are
|
|
502
|
+
- Apps (plugins) are expected to be documented within their package at
|
|
503
|
+
``docs/models/{model_name}.html`` and are served dynamically through
|
|
504
|
+
the ``AppDocsView`` endpoint (``/docs/<app_name>/<path>``).
|
|
491
505
|
|
|
492
506
|
Any model can define a `documentation_static_path` class attribute if it needs to override the above expectations.
|
|
493
507
|
|
|
@@ -501,16 +515,36 @@ def get_docs_url(model):
|
|
|
501
515
|
|
|
502
516
|
Example:
|
|
503
517
|
>>> get_docs_url(location_instance)
|
|
504
|
-
"static/docs/
|
|
518
|
+
"static/docs/user-guide/core-data-model/dcim/location.html"
|
|
519
|
+
>>> get_docs_url(virtual_server_instance)
|
|
520
|
+
"static/docs/user-guide/core-data-model/load-balancers/virtualserver.html"
|
|
521
|
+
>>> get_docs_url(example_model)
|
|
522
|
+
"/docs/example-app/models/examplemodel.html"
|
|
505
523
|
"""
|
|
506
524
|
if hasattr(model, "documentation_static_path"):
|
|
507
525
|
path = model.documentation_static_path
|
|
508
526
|
elif model._meta.app_label in settings.PLUGINS:
|
|
527
|
+
app_label = model._meta.app_label
|
|
528
|
+
app_config = apps.get_app_config(app_label)
|
|
529
|
+
app_base_url = getattr(app_config, "base_url", None) or app_config.label
|
|
530
|
+
path = f"models/{model._meta.model_name}.html"
|
|
531
|
+
# Check that the file actually exists inside the app's docs folder
|
|
532
|
+
try:
|
|
533
|
+
base_dir = resources.files(app_label) / "docs"
|
|
534
|
+
file_path = base_dir / path
|
|
535
|
+
if file_path.is_file():
|
|
536
|
+
return reverse("docs_file", kwargs={"app_base_url": app_base_url, "path": path})
|
|
537
|
+
except ModuleNotFoundError:
|
|
538
|
+
pass
|
|
539
|
+
logger.debug("No documentation found for %s (expected at %s)", type(model), path)
|
|
540
|
+
# define path to try to get static
|
|
509
541
|
path = f"{model._meta.app_label}/docs/models/{model._meta.model_name}.html"
|
|
510
542
|
elif model._meta.app_label == "extras":
|
|
511
543
|
path = f"docs/user-guide/platform-functionality/{model._meta.model_name}.html"
|
|
512
544
|
else:
|
|
513
|
-
path =
|
|
545
|
+
path = (
|
|
546
|
+
f"docs/user-guide/core-data-model/{model._meta.app_label.replace('_', '-')}/{model._meta.model_name}.html"
|
|
547
|
+
)
|
|
514
548
|
|
|
515
549
|
# Check to see if documentation exists in any of the static paths.
|
|
516
550
|
if find(path):
|
|
@@ -759,7 +793,7 @@ def render_address(address):
|
|
|
759
793
|
quote_plus(address),
|
|
760
794
|
)
|
|
761
795
|
address = format_html_join("", "{}<br>", ((line,) for line in address.split("\n")))
|
|
762
|
-
return format_html('<div class="
|
|
796
|
+
return format_html('<div class="float-end d-print-none">{}</div>{}', map_link, address)
|
|
763
797
|
return HTML_NONE
|
|
764
798
|
|
|
765
799
|
|
|
@@ -798,7 +832,11 @@ def render_button_class(value):
|
|
|
798
832
|
"""
|
|
799
833
|
if value:
|
|
800
834
|
base = value.split()[0]
|
|
801
|
-
return format_html(
|
|
835
|
+
return format_html(
|
|
836
|
+
'<button class="btn btn-{}">{}</button>',
|
|
837
|
+
base.lower() if base.lower() != "default" else "secondary",
|
|
838
|
+
base.capitalize(),
|
|
839
|
+
)
|
|
802
840
|
return ""
|
|
803
841
|
|
|
804
842
|
|
|
@@ -831,6 +869,26 @@ def label_list(value, suffix=""):
|
|
|
831
869
|
)
|
|
832
870
|
|
|
833
871
|
|
|
872
|
+
@library.filter()
|
|
873
|
+
@register.filter()
|
|
874
|
+
def format_timezone(time_zone):
|
|
875
|
+
"""
|
|
876
|
+
Return a human-readable representation of a time zone including:
|
|
877
|
+
- Time zone name and UTC offset on the first line
|
|
878
|
+
- Local date and time on the next line (in smaller font)
|
|
879
|
+
"""
|
|
880
|
+
if not time_zone:
|
|
881
|
+
return HTML_NONE
|
|
882
|
+
|
|
883
|
+
now = datetime.datetime.now(time_zone)
|
|
884
|
+
|
|
885
|
+
# Locale-aware formatting (respects USE_L10N + active locale)
|
|
886
|
+
local_time = date_format(now, format="DATETIME_FORMAT", use_l10n=True)
|
|
887
|
+
|
|
888
|
+
result = f"{time_zone} (UTC {now.strftime('%z')})<br><small>{_('Local time')}: {local_time}</small>"
|
|
889
|
+
return format_html(result)
|
|
890
|
+
|
|
891
|
+
|
|
834
892
|
#
|
|
835
893
|
# Tags
|
|
836
894
|
#
|
|
@@ -899,7 +957,7 @@ def django_querystring(context, query_dict=None, **kwargs):
|
|
|
899
957
|
{% django_querystring my_query_dict foo=3 %}
|
|
900
958
|
"""
|
|
901
959
|
if query_dict is None:
|
|
902
|
-
query_dict = context
|
|
960
|
+
query_dict = context["request"].GET
|
|
903
961
|
params = query_dict.copy()
|
|
904
962
|
for key, value in kwargs.items():
|
|
905
963
|
if value is None:
|
|
@@ -932,7 +990,7 @@ def table_config_button(table, table_name=None, extra_classes="", disabled=False
|
|
|
932
990
|
<span class="mdi mdi-cog" aria-hidden="true"></span>
|
|
933
991
|
<span class="visually-hidden">Configure</span>
|
|
934
992
|
</button>"""
|
|
935
|
-
return format_html(html_template, extra_classes, table_name,
|
|
993
|
+
return format_html(html_template, extra_classes, table_name, "disabled" if disabled else "", table_name)
|
|
936
994
|
|
|
937
995
|
|
|
938
996
|
@register.inclusion_tag("utilities/templatetags/utilization_graph.html")
|
nautobot/core/testing/api.py
CHANGED
|
@@ -17,6 +17,7 @@ from rest_framework import serializers, status
|
|
|
17
17
|
from rest_framework.relations import ManyRelatedField
|
|
18
18
|
from rest_framework.test import APITransactionTestCase as _APITransactionTestCase
|
|
19
19
|
|
|
20
|
+
from nautobot.core import constants
|
|
20
21
|
from nautobot.core.api.utils import get_serializer_for_model
|
|
21
22
|
from nautobot.core.models import fields as core_fields
|
|
22
23
|
from nautobot.core.models.tree_queries import TreeModel
|
|
@@ -294,8 +295,9 @@ class APIViewTestCases:
|
|
|
294
295
|
m2m_fields = self.get_m2m_fields()
|
|
295
296
|
self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
|
|
296
297
|
list_url = f"{self._get_list_url()}?depth=0"
|
|
298
|
+
# With exclude_m2m query parameter set to False
|
|
297
299
|
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
298
|
-
response = self.client.get(list_url, **self.header)
|
|
300
|
+
response = self.client.get(list_url + "&exclude_m2m=false", **self.header)
|
|
299
301
|
base_num_queries = len(cqc)
|
|
300
302
|
|
|
301
303
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
@@ -340,9 +342,9 @@ class APIViewTestCases:
|
|
|
340
342
|
app_label, model_name = object_type.split(".")
|
|
341
343
|
ContentType.objects.get(app_label=app_label, model=model_name)
|
|
342
344
|
|
|
343
|
-
|
|
345
|
+
# With exclude_m2m query parameter set to True
|
|
344
346
|
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
345
|
-
response = self.client.get(list_url, **self.header)
|
|
347
|
+
response = self.client.get(list_url + "&exclude_m2m=true", **self.header)
|
|
346
348
|
|
|
347
349
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
348
350
|
self.assertIsInstance(response.data, dict)
|
|
@@ -387,8 +389,9 @@ class APIViewTestCases:
|
|
|
387
389
|
m2m_fields = self.get_m2m_fields()
|
|
388
390
|
self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
|
|
389
391
|
list_url = f"{self._get_list_url()}?depth=1"
|
|
392
|
+
# With exclude_m2m query parameter set to False
|
|
390
393
|
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
391
|
-
response = self.client.get(list_url, **self.header)
|
|
394
|
+
response = self.client.get(list_url + "&exclude_m2m=false", **self.header)
|
|
392
395
|
base_num_queries = len(cqc)
|
|
393
396
|
|
|
394
397
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
@@ -420,9 +423,9 @@ class APIViewTestCases:
|
|
|
420
423
|
self.assertTrue(is_uuid(response_data[field]["id"]))
|
|
421
424
|
self.assertGreater(len(response_data[field].keys()), 3, response_data[field])
|
|
422
425
|
|
|
423
|
-
|
|
426
|
+
# With exclude_m2m query parameter set to True
|
|
424
427
|
with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
|
|
425
|
-
response = self.client.get(list_url, **self.header)
|
|
428
|
+
response = self.client.get(list_url + "&exclude_m2m=true", **self.header)
|
|
426
429
|
|
|
427
430
|
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
428
431
|
self.assertIsInstance(response.data, dict)
|
|
@@ -458,6 +461,54 @@ class APIViewTestCases:
|
|
|
458
461
|
self.assertNotIn(field, response_data)
|
|
459
462
|
# TODO: we should assert that all other fields are still present, but there's a few corner cases...
|
|
460
463
|
|
|
464
|
+
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
465
|
+
def test_list_objects_exclude_m2m(self):
|
|
466
|
+
"""
|
|
467
|
+
GET a list of objects with or without the "exclude_m2m" parameter.
|
|
468
|
+
|
|
469
|
+
With exclude_m2m query parameter set to True, we should see no many-to-many fields.
|
|
470
|
+
With exclude_m2m query parameter set to False, we should see all many-to-many fields.
|
|
471
|
+
With exclude_m2m query parameter not set, we should only see the default many-to-many fields.
|
|
472
|
+
"""
|
|
473
|
+
m2m_fields = self.get_m2m_fields()
|
|
474
|
+
if not m2m_fields:
|
|
475
|
+
self.skipTest("No many-to-many fields to test")
|
|
476
|
+
self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
|
|
477
|
+
list_url = f"{self._get_list_url()}"
|
|
478
|
+
|
|
479
|
+
# With exclude_m2m query parameter not set
|
|
480
|
+
response = self.client.get(list_url, **self.header)
|
|
481
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
482
|
+
self.assertIsInstance(response.data, dict)
|
|
483
|
+
self.assertIn("results", response.data)
|
|
484
|
+
|
|
485
|
+
for response_data in response.data["results"]:
|
|
486
|
+
for field in m2m_fields:
|
|
487
|
+
if field in constants.DEFAULT_M2M_FIELDS:
|
|
488
|
+
self.assertIn(field, response_data)
|
|
489
|
+
self.assertIsInstance(response_data[field], list)
|
|
490
|
+
else:
|
|
491
|
+
self.assertNotIn(field, response_data)
|
|
492
|
+
|
|
493
|
+
# With exclude_m2m query parameter set to True
|
|
494
|
+
response = self.client.get(list_url + "?exclude_m2m=true", **self.header)
|
|
495
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
496
|
+
self.assertIsInstance(response.data, dict)
|
|
497
|
+
self.assertIn("results", response.data)
|
|
498
|
+
for response_data in response.data["results"]:
|
|
499
|
+
for field in m2m_fields:
|
|
500
|
+
self.assertNotIn(field, response_data)
|
|
501
|
+
|
|
502
|
+
# With exclude_m2m query parameter set to False
|
|
503
|
+
response = self.client.get(list_url + "?exclude_m2m=false", **self.header)
|
|
504
|
+
self.assertHttpStatus(response, status.HTTP_200_OK)
|
|
505
|
+
self.assertIsInstance(response.data, dict)
|
|
506
|
+
self.assertIn("results", response.data)
|
|
507
|
+
for response_data in response.data["results"]:
|
|
508
|
+
for field in m2m_fields:
|
|
509
|
+
self.assertIn(field, response_data)
|
|
510
|
+
self.assertIsInstance(response_data[field], list)
|
|
511
|
+
|
|
461
512
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
|
462
513
|
def test_list_objects_without_permission(self):
|
|
463
514
|
"""
|
|
@@ -1076,12 +1127,20 @@ class APIViewTestCases:
|
|
|
1076
1127
|
|
|
1077
1128
|
self.assertIn("actions", data)
|
|
1078
1129
|
|
|
1079
|
-
# Grab any field that has choices defined (fields with enums)
|
|
1130
|
+
# Grab any field that has choices defined (fields with enums including child fields with enums)
|
|
1080
1131
|
field_choices = {}
|
|
1081
1132
|
if "POST" in data["actions"]:
|
|
1082
|
-
field_choices = {
|
|
1133
|
+
field_choices = {
|
|
1134
|
+
k
|
|
1135
|
+
for k, v in data["actions"]["POST"].items()
|
|
1136
|
+
if "choices" in v or ("child" in v and "choices" in v["child"])
|
|
1137
|
+
}
|
|
1083
1138
|
elif "PUT" in data["actions"]:
|
|
1084
|
-
field_choices = {
|
|
1139
|
+
field_choices = {
|
|
1140
|
+
k
|
|
1141
|
+
for k, v in data["actions"]["PUT"].items()
|
|
1142
|
+
if "choices" in v or ("child" in v and "choices" in v["child"])
|
|
1143
|
+
}
|
|
1085
1144
|
else:
|
|
1086
1145
|
self.fail(f"Neither PUT nor POST are available actions in: {data['actions']}")
|
|
1087
1146
|
|
nautobot/core/testing/filters.py
CHANGED
|
@@ -21,7 +21,6 @@ from nautobot.core.filters import (
|
|
|
21
21
|
)
|
|
22
22
|
from nautobot.core.models.generics import PrimaryModel
|
|
23
23
|
from nautobot.core.testing import views
|
|
24
|
-
from nautobot.core.utils.deprecation import class_deprecated_in_favor_of
|
|
25
24
|
from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Tag, Team
|
|
26
25
|
from nautobot.tenancy import models
|
|
27
26
|
|
|
@@ -435,28 +434,6 @@ class FilterTestCases:
|
|
|
435
434
|
),
|
|
436
435
|
)
|
|
437
436
|
|
|
438
|
-
# Test cases should just explicitly include `name` as a generic_filter_tests entry
|
|
439
|
-
@class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
|
|
440
|
-
class NameOnlyFilterTestCase(FilterTestCase):
|
|
441
|
-
"""Add simple tests for filtering by name."""
|
|
442
|
-
|
|
443
|
-
def test_filters_generic(self):
|
|
444
|
-
if not any(test[0] == "name" for test in self.generic_filter_tests):
|
|
445
|
-
self.generic_filter_tests = (["name"], *self.generic_filter_tests)
|
|
446
|
-
super().test_filters_generic()
|
|
447
|
-
|
|
448
|
-
# Test cases should just explicitly include `name` and `slug` as generic_filter_tests entries
|
|
449
|
-
@class_deprecated_in_favor_of(FilterTestCase) # pylint: disable=undefined-variable
|
|
450
|
-
class NameSlugFilterTestCase(FilterTestCase):
|
|
451
|
-
"""Add simple tests for filtering by name and by slug."""
|
|
452
|
-
|
|
453
|
-
def test_filters_generic(self):
|
|
454
|
-
if not any(test[0] == "slug" for test in self.generic_filter_tests):
|
|
455
|
-
self.generic_filter_tests = (["slug"], *self.generic_filter_tests)
|
|
456
|
-
if not any(test[0] == "name" for test in self.generic_filter_tests):
|
|
457
|
-
self.generic_filter_tests = (["name"], *self.generic_filter_tests)
|
|
458
|
-
super().test_filters_generic()
|
|
459
|
-
|
|
460
437
|
class TenancyFilterTestCaseMixin(views.TestCase):
|
|
461
438
|
"""Add test cases for tenant and tenant-group filters."""
|
|
462
439
|
|