nautobot 2.0.0a3__py3-none-any.whl → 2.0.0b1__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/api.py +6 -8
- nautobot/apps/forms.py +0 -2
- nautobot/apps/ui.py +0 -8
- nautobot/circuits/api/serializers.py +9 -117
- nautobot/circuits/api/urls.py +1 -1
- nautobot/circuits/api/views.py +0 -1
- nautobot/circuits/forms.py +0 -65
- nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
- nautobot/circuits/migrations/0016_tagsfield.py +34 -0
- nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
- nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
- nautobot/circuits/models.py +3 -87
- nautobot/circuits/navigation.py +14 -69
- nautobot/circuits/signals.py +0 -2
- nautobot/circuits/tables.py +39 -1
- nautobot/circuits/tests/integration/test_relationships.py +9 -9
- nautobot/circuits/tests/test_api.py +4 -8
- nautobot/circuits/tests/test_filters.py +10 -4
- nautobot/circuits/tests/test_models.py +5 -1
- nautobot/circuits/tests/test_views.py +27 -5
- nautobot/circuits/views.py +18 -10
- nautobot/core/api/__init__.py +8 -2
- nautobot/core/api/fields.py +15 -6
- nautobot/core/api/filter_backends.py +3 -2
- nautobot/core/api/metadata.py +237 -30
- nautobot/core/api/mixins.py +94 -0
- nautobot/core/api/pagination.py +4 -0
- nautobot/core/api/parsers.py +154 -0
- nautobot/core/api/renderers.py +153 -2
- nautobot/core/api/schema.py +46 -2
- nautobot/core/api/serializers.py +377 -35
- nautobot/core/api/urls.py +11 -3
- nautobot/core/api/utils.py +174 -2
- nautobot/core/api/versioning.py +32 -10
- nautobot/core/api/views.py +266 -72
- nautobot/core/apps/__init__.py +138 -220
- nautobot/core/celery/__init__.py +112 -41
- nautobot/core/celery/backends.py +19 -12
- nautobot/core/celery/control.py +46 -0
- nautobot/core/celery/encoders.py +53 -0
- nautobot/core/celery/log.py +38 -0
- nautobot/core/celery/schedulers.py +23 -4
- nautobot/core/celery/task.py +1 -16
- nautobot/core/checks.py +0 -27
- nautobot/core/choices.py +0 -113
- nautobot/core/{cli.py → cli/__init__.py} +1 -1
- nautobot/core/cli/__main__.py +3 -0
- nautobot/core/constants.py +0 -24
- nautobot/core/context_processors.py +12 -0
- nautobot/core/filters.py +2 -2
- nautobot/core/forms/__init__.py +0 -4
- nautobot/core/forms/fields.py +38 -65
- nautobot/core/forms/forms.py +4 -1
- nautobot/core/forms/utils.py +0 -52
- nautobot/core/graphql/schema.py +4 -27
- nautobot/core/jobs/__init__.py +75 -0
- nautobot/core/management/commands/build_ui.py +255 -0
- nautobot/core/management/commands/generate_test_data.py +3 -2
- nautobot/core/management/commands/post_upgrade.py +24 -24
- nautobot/core/models/__init__.py +26 -1
- nautobot/core/models/fields.py +24 -5
- nautobot/core/models/generics.py +2 -42
- nautobot/core/models/managers.py +5 -0
- nautobot/core/models/name_color_content_types.py +0 -14
- nautobot/core/models/tree_queries.py +14 -4
- nautobot/core/models/utils.py +5 -6
- nautobot/core/models/validators.py +17 -8
- nautobot/core/releases.py +8 -10
- nautobot/core/settings.py +80 -42
- nautobot/core/tables.py +5 -5
- nautobot/core/tasks.py +4 -7
- nautobot/core/templates/base.html +1 -49
- nautobot/core/templates/base_django.html +49 -0
- nautobot/core/templates/base_react.html +55 -0
- nautobot/core/templates/buttons/export.html +6 -4
- nautobot/core/templates/generic/object_bulk_create.html +10 -21
- nautobot/core/templates/generic/object_list.html +3 -1
- nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
- nautobot/core/templates/inc/footer.html +1 -0
- nautobot/core/templates/inc/javascript.html +0 -14
- nautobot/core/templates/inc/nav_menu.html +28 -33
- nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
- nautobot/core/templates/inc/relationships_table_rows.html +2 -2
- nautobot/core/templates/nautobot_config.py.j2 +8 -20
- nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
- nautobot/core/templates/rest_framework/api.html +8 -0
- nautobot/core/templatetags/buttons.py +32 -28
- nautobot/core/testing/__init__.py +47 -44
- nautobot/core/testing/api.py +362 -47
- nautobot/core/testing/filters.py +1 -1
- nautobot/core/testing/migrations.py +2 -0
- nautobot/core/testing/mixins.py +22 -9
- nautobot/core/testing/schema.py +2 -1
- nautobot/core/testing/views.py +21 -46
- nautobot/core/tests/integration/test_filters.py +17 -8
- nautobot/core/tests/integration/test_navbar.py +11 -34
- nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
- nautobot/core/tests/nautobot_config.py +2 -3
- nautobot/core/tests/test_api.py +290 -21
- nautobot/core/tests/test_checks.py +0 -7
- nautobot/core/tests/test_filters.py +107 -59
- nautobot/core/tests/test_forms.py +26 -92
- nautobot/core/tests/test_graphql.py +110 -77
- nautobot/core/tests/test_logging.py +4 -0
- nautobot/core/tests/test_managers.py +3 -1
- nautobot/core/tests/test_models.py +2 -0
- nautobot/core/tests/test_paginator.py +3 -1
- nautobot/core/tests/test_releases.py +12 -12
- nautobot/core/tests/test_templatetags_helpers.py +4 -4
- nautobot/core/tests/test_utils.py +32 -68
- nautobot/core/tests/test_views.py +12 -15
- nautobot/core/utils/data.py +17 -0
- nautobot/core/utils/deprecation.py +9 -6
- nautobot/core/utils/filtering.py +8 -3
- nautobot/core/utils/git.py +12 -4
- nautobot/core/utils/lookup.py +3 -1
- nautobot/core/utils/requests.py +1 -104
- nautobot/core/views/__init__.py +1 -0
- nautobot/core/views/generic.py +75 -110
- nautobot/core/views/mixins.py +52 -61
- nautobot/core/views/renderers.py +6 -7
- nautobot/core/views/utils.py +80 -0
- nautobot/dcim/api/serializers.py +160 -667
- nautobot/dcim/api/urls.py +1 -1
- nautobot/dcim/api/views.py +7 -44
- nautobot/dcim/choices.py +2 -0
- nautobot/dcim/filters/__init__.py +21 -0
- nautobot/dcim/form_mixins.py +1 -27
- nautobot/dcim/forms.py +19 -765
- nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
- nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
- nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
- nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
- nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
- nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
- nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
- nautobot/dcim/migrations/0040_tagsfield.py +109 -0
- nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
- nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
- nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
- nautobot/dcim/models/cables.py +3 -33
- nautobot/dcim/models/device_component_templates.py +6 -0
- nautobot/dcim/models/device_components.py +12 -198
- nautobot/dcim/models/devices.py +30 -143
- nautobot/dcim/models/locations.py +3 -64
- nautobot/dcim/models/power.py +3 -50
- nautobot/dcim/models/racks.py +7 -84
- nautobot/dcim/navigation.py +141 -467
- nautobot/dcim/signals.py +0 -2
- nautobot/dcim/tables/locations.py +2 -2
- nautobot/dcim/tables/power.py +1 -2
- nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/devicetype.html +2 -2
- nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/location.html +16 -1
- nautobot/dcim/templates/dcim/locationtype.html +15 -0
- nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
- nautobot/dcim/templates/dcim/rackgroup.html +0 -12
- nautobot/dcim/tests/test_api.py +166 -81
- nautobot/dcim/tests/test_cablepaths.py +41 -35
- nautobot/dcim/tests/test_filters.py +67 -23
- nautobot/dcim/tests/test_forms.py +5 -205
- nautobot/dcim/tests/test_graphql.py +7 -2
- nautobot/dcim/tests/test_migrations.py +6 -11
- nautobot/dcim/tests/test_models.py +182 -110
- nautobot/dcim/tests/test_natural_ordering.py +11 -8
- nautobot/dcim/tests/test_signals.py +6 -3
- nautobot/dcim/tests/test_views.py +197 -175
- nautobot/dcim/urls.py +11 -16
- nautobot/dcim/views.py +7 -134
- nautobot/docs/additional-features/caching.md +6 -87
- nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
- nautobot/docs/additional-features/jobs.md +177 -195
- nautobot/docs/administration/nautobot-server.md +6 -21
- nautobot/docs/administration/replicating-nautobot.md +0 -10
- nautobot/docs/configuration/optional-settings.md +32 -41
- nautobot/docs/configuration/required-settings.md +11 -52
- nautobot/docs/development/application-registry.md +2 -13
- nautobot/docs/development/extending-models.md +15 -17
- nautobot/docs/development/generic-views.md +0 -2
- nautobot/docs/development/getting-started.md +55 -5
- nautobot/docs/development/navigation-menu.md +22 -93
- nautobot/docs/development/react-ui.md +105 -0
- nautobot/docs/development/role-internals.md +1 -3
- nautobot/docs/development/style-guide.md +6 -4
- nautobot/docs/index.md +3 -2
- nautobot/docs/installation/migrating-from-netbox.md +11 -42
- nautobot/docs/installation/nautobot.md +1 -1
- nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
- nautobot/docs/models/dcim/device.md +3 -0
- nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
- nautobot/docs/models/extras/computedfield.md +4 -4
- nautobot/docs/models/extras/gitrepository.md +3 -0
- nautobot/docs/models/extras/job.md +1 -0
- nautobot/docs/models/extras/jobbutton.md +18 -13
- nautobot/docs/models/extras/jobhook.md +7 -4
- nautobot/docs/models/extras/jobresult.md +6 -2
- nautobot/docs/models/extras/relationship.md +2 -2
- nautobot/docs/models/extras/status.md +6 -19
- nautobot/docs/models/ipam/ipaddress.md +3 -0
- nautobot/docs/models/virtualization/virtualmachine.md +3 -0
- nautobot/docs/plugins/development.md +83 -21
- nautobot/docs/release-notes/version-1.5.md +53 -0
- nautobot/docs/release-notes/version-2.0.md +180 -0
- nautobot/docs/requirements.txt +1 -0
- nautobot/docs/rest-api/overview.md +384 -215
- nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
- nautobot/extras/admin.py +3 -5
- nautobot/extras/api/customfields.py +15 -39
- nautobot/extras/api/fields.py +0 -11
- nautobot/extras/api/mixins.py +45 -0
- nautobot/extras/api/relationships.py +63 -158
- nautobot/extras/api/serializers.py +165 -700
- nautobot/extras/api/urls.py +1 -1
- nautobot/extras/api/views.py +294 -280
- nautobot/extras/apps.py +4 -7
- nautobot/extras/choices.py +11 -9
- nautobot/extras/constants.py +9 -3
- nautobot/extras/datasources/__init__.py +2 -0
- nautobot/extras/datasources/git.py +135 -186
- nautobot/extras/datasources/registry.py +25 -35
- nautobot/extras/filters/__init__.py +20 -19
- nautobot/extras/filters/mixins.py +4 -4
- nautobot/extras/forms/forms.py +63 -127
- nautobot/extras/forms/mixins.py +23 -51
- nautobot/extras/health_checks.py +0 -33
- nautobot/extras/jobs.py +387 -565
- nautobot/extras/management/commands/runjob.py +24 -62
- nautobot/extras/managers.py +30 -7
- nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
- nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
- nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
- nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
- nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
- nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
- nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
- nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
- nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
- nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
- nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
- nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -1
- nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
- nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
- nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
- nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
- nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
- nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
- nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
- nautobot/extras/migrations/{0076_remove_name_field_and_make_label_field_non_nullable.py → 0077_remove_name_field_and_make_label_field_non_nullable.py} +1 -1
- nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
- nautobot/extras/migrations/0079_tagsfield.py +28 -0
- nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
- nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
- nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
- nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
- nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
- nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
- nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
- nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
- nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
- nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
- nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
- nautobot/extras/models/__init__.py +2 -3
- nautobot/extras/models/change_logging.py +0 -36
- nautobot/extras/models/customfields.py +39 -33
- nautobot/extras/models/datasources.py +48 -50
- nautobot/extras/models/groups.py +5 -6
- nautobot/extras/models/jobs.py +189 -321
- nautobot/extras/models/mixins.py +0 -71
- nautobot/extras/models/models.py +0 -19
- nautobot/extras/models/relationships.py +19 -13
- nautobot/extras/models/roles.py +0 -34
- nautobot/extras/models/secrets.py +2 -26
- nautobot/extras/models/statuses.py +6 -5
- nautobot/extras/models/tags.py +2 -17
- nautobot/extras/navigation.py +89 -307
- nautobot/extras/plugins/__init__.py +3 -120
- nautobot/extras/plugins/utils.py +0 -3
- nautobot/extras/plugins/validators.py +5 -4
- nautobot/extras/plugins/views.py +16 -3
- nautobot/extras/querysets.py +1 -7
- nautobot/extras/registry.py +3 -0
- nautobot/extras/signals.py +26 -60
- nautobot/extras/tables.py +34 -40
- nautobot/extras/tasks.py +0 -12
- nautobot/extras/templates/extras/configcontext.html +1 -1
- nautobot/extras/templates/extras/configcontextschema.html +16 -1
- nautobot/extras/templates/extras/customfield.html +0 -13
- nautobot/extras/templates/extras/gitrepository.html +3 -3
- nautobot/extras/templates/extras/inc/jobresult.html +10 -0
- nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
- nautobot/extras/templates/extras/job.html +35 -25
- nautobot/extras/templates/extras/job_approval_request.html +15 -30
- nautobot/extras/templates/extras/job_detail.html +13 -31
- nautobot/extras/templates/extras/job_edit.html +15 -17
- nautobot/extras/templates/extras/jobresult.html +24 -6
- nautobot/extras/templates/extras/scheduledjob.html +2 -2
- nautobot/extras/templates/extras/secret.html +28 -0
- nautobot/extras/templatetags/job_buttons.py +1 -0
- nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
- nautobot/extras/test_jobs/atomic_transaction.py +53 -0
- nautobot/extras/test_jobs/dry_run.py +29 -0
- nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
- nautobot/extras/test_jobs/duplicate_name2.py +9 -0
- nautobot/extras/test_jobs/fail.py +23 -0
- nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
- nautobot/extras/test_jobs/file_upload_pass.py +25 -0
- nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
- nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
- nautobot/extras/test_jobs/job_button_receiver.py +28 -0
- nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
- nautobot/extras/test_jobs/job_variables.py +88 -0
- nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
- nautobot/extras/test_jobs/log_redaction.py +20 -0
- nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
- nautobot/extras/test_jobs/modify_db.py +25 -0
- nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
- nautobot/extras/test_jobs/object_var_optional.py +21 -0
- nautobot/extras/test_jobs/object_var_required.py +21 -0
- nautobot/extras/test_jobs/object_vars.py +26 -0
- nautobot/extras/test_jobs/pass.py +25 -0
- nautobot/extras/test_jobs/profiling.py +32 -0
- nautobot/extras/test_jobs/read_only_job.py +15 -0
- nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
- nautobot/extras/{tests/example_jobs/test_soft_time_limit_greater_than_time_limit.py → test_jobs/soft_time_limit_greater_than_time_limit.py} +5 -1
- nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
- nautobot/extras/tests/integration/test_computedfields.py +1 -1
- nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
- nautobot/extras/tests/integration/test_customfields.py +4 -2
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_jobs.py +25 -27
- nautobot/extras/tests/integration/test_notes.py +8 -4
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_api.py +649 -642
- nautobot/extras/tests/test_changelog.py +3 -3
- nautobot/extras/tests/test_context_managers.py +5 -3
- nautobot/extras/tests/test_customfields.py +92 -50
- nautobot/extras/tests/test_datasources.py +189 -112
- nautobot/extras/tests/test_dynamicgroups.py +7 -8
- nautobot/extras/tests/test_filters.py +137 -89
- nautobot/extras/tests/test_forms.py +73 -75
- nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
- nautobot/extras/tests/test_jobs.py +262 -263
- nautobot/extras/tests/test_migrations.py +4 -3
- nautobot/extras/tests/test_models.py +116 -161
- nautobot/extras/tests/test_plugins.py +38 -60
- nautobot/extras/tests/test_relationships.py +167 -120
- nautobot/extras/tests/test_tags.py +6 -11
- nautobot/extras/tests/test_utils.py +31 -1
- nautobot/extras/tests/test_views.py +201 -145
- nautobot/extras/tests/test_webhooks.py +6 -2
- nautobot/extras/urls.py +42 -42
- nautobot/extras/utils.py +137 -163
- nautobot/extras/views.py +78 -152
- nautobot/ipam/api/fields.py +17 -0
- nautobot/ipam/api/serializers.py +58 -164
- nautobot/ipam/api/urls.py +1 -1
- nautobot/ipam/api/views.py +3 -2
- nautobot/ipam/apps.py +1 -2
- nautobot/ipam/filters.py +1 -10
- nautobot/ipam/forms.py +4 -177
- nautobot/ipam/lookups.py +1 -0
- nautobot/ipam/management/commands/__init__.py +0 -0
- nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
- nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
- nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
- nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
- nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
- nautobot/ipam/migrations/0028_tagsfield.py +44 -0
- nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
- nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
- nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
- nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
- nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
- nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
- nautobot/ipam/models.py +100 -236
- nautobot/ipam/navigation.py +36 -181
- nautobot/ipam/querysets.py +20 -25
- nautobot/ipam/signals.py +49 -6
- nautobot/ipam/tables.py +10 -3
- nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
- nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
- nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
- nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
- nautobot/ipam/templates/ipam/prefix.html +1 -1
- nautobot/ipam/templates/ipam/vlangroup.html +0 -13
- nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
- nautobot/ipam/tests/integration/test_prefixes.py +3 -26
- nautobot/ipam/tests/test_api.py +22 -19
- nautobot/ipam/tests/test_filters.py +59 -23
- nautobot/ipam/tests/test_migrations.py +6 -10
- nautobot/ipam/tests/test_models.py +323 -198
- nautobot/ipam/tests/test_ordering.py +2 -2
- nautobot/ipam/tests/test_querysets.py +44 -24
- nautobot/ipam/tests/test_views.py +73 -26
- nautobot/ipam/urls.py +16 -0
- nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
- nautobot/ipam/utils/migrations.py +713 -0
- nautobot/ipam/views.py +137 -20
- nautobot/project-static/docs/404.html +1178 -10
- nautobot/project-static/docs/additional-features/caching.html +1224 -159
- nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
- nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
- nautobot/project-static/docs/additional-features/graphql.html +1179 -11
- nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
- nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
- nautobot/project-static/docs/additional-features/jobs.html +1514 -328
- nautobot/project-static/docs/additional-features/napalm.html +1180 -12
- nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
- nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
- nautobot/project-static/docs/administration/celery-queues.html +1178 -10
- nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
- nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
- nautobot/project-static/docs/administration/permissions.html +1178 -10
- nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
- nautobot/project-static/docs/apps/index.html +1178 -10
- nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
- nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
- nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
- nautobot/project-static/docs/configuration/index.html +1178 -10
- nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
- nautobot/project-static/docs/configuration/required-settings.html +1312 -211
- nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
- nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
- nautobot/project-static/docs/core-functionality/devices.html +1182 -10
- nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
- nautobot/project-static/docs/core-functionality/power.html +1178 -10
- nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
- nautobot/project-static/docs/core-functionality/services.html +1178 -10
- nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
- nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
- nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
- nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
- nautobot/project-static/docs/development/application-registry.html +1190 -42
- nautobot/project-static/docs/development/best-practices.html +1178 -10
- nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
- nautobot/project-static/docs/development/extending-models.html +1238 -83
- nautobot/project-static/docs/development/generic-views.html +1180 -14
- nautobot/project-static/docs/development/getting-started.html +1365 -90
- nautobot/project-static/docs/development/homepage.html +1178 -10
- nautobot/project-static/docs/development/index.html +1178 -10
- nautobot/project-static/docs/development/model-features.html +1178 -10
- nautobot/project-static/docs/development/natural-keys.html +1178 -10
- nautobot/project-static/docs/development/navigation-menu.html +1215 -125
- nautobot/project-static/docs/development/react-ui.html +4199 -0
- nautobot/project-static/docs/development/release-checklist.html +1178 -10
- nautobot/project-static/docs/development/role-internals.html +1179 -12
- nautobot/project-static/docs/development/style-guide.html +1188 -19
- nautobot/project-static/docs/development/templates.html +1178 -10
- nautobot/project-static/docs/development/testing.html +1178 -10
- nautobot/project-static/docs/development/user-preferences.html +1178 -10
- nautobot/project-static/docs/docker/index.html +1178 -10
- nautobot/project-static/docs/index.html +1183 -12
- nautobot/project-static/docs/installation/centos.html +1178 -10
- nautobot/project-static/docs/installation/external-authentication.html +1178 -10
- nautobot/project-static/docs/installation/http-server.html +1178 -10
- nautobot/project-static/docs/installation/index.html +1178 -10
- nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
- nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
- nautobot/project-static/docs/installation/nautobot.html +1179 -11
- nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
- nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
- nautobot/project-static/docs/installation/services.html +1178 -10
- nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
- nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
- nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
- nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
- nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
- nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
- nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
- nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
- nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
- nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
- nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
- nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
- nautobot/project-static/docs/installation/ubuntu.html +1178 -10
- nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
- nautobot/project-static/docs/installation/upgrading.html +1178 -10
- nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
- nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
- nautobot/project-static/docs/models/circuits/provider.html +1293 -103
- nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
- nautobot/project-static/docs/models/dcim/cable.html +1324 -103
- nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
- nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/device.html +1326 -132
- nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
- nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
- nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
- nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/interface.html +1293 -103
- nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
- nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
- nautobot/project-static/docs/models/dcim/location.html +1293 -103
- nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
- nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
- nautobot/project-static/docs/models/dcim/platform.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
- nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
- nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
- nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
- nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
- nautobot/project-static/docs/models/dcim/rack.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
- nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
- nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
- nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
- nautobot/project-static/docs/models/dcim/region.html +1178 -10
- nautobot/project-static/docs/models/dcim/site.html +1178 -10
- nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
- nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
- nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
- nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
- nautobot/project-static/docs/models/extras/customfield.html +1180 -12
- nautobot/project-static/docs/models/extras/customlink.html +1180 -12
- nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
- nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
- nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
- nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
- nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
- nautobot/project-static/docs/models/extras/job.html +1277 -86
- nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
- nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
- nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
- nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
- nautobot/project-static/docs/models/extras/note.html +1180 -12
- nautobot/project-static/docs/models/extras/relationship.html +1182 -14
- nautobot/project-static/docs/models/extras/role.html +1320 -86
- nautobot/project-static/docs/models/extras/secret.html +1314 -86
- nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
- nautobot/project-static/docs/models/extras/status.html +1188 -59
- nautobot/project-static/docs/models/extras/tag.html +1180 -12
- nautobot/project-static/docs/models/extras/webhook.html +1180 -12
- nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
- nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
- nautobot/project-static/docs/models/ipam/rir.html +1276 -86
- nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
- nautobot/project-static/docs/models/ipam/service.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
- nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
- nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
- nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
- nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
- nautobot/project-static/docs/models/users/token.html +1276 -86
- nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
- nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
- nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
- nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/plugins/development.html +1726 -495
- nautobot/project-static/docs/plugins/index.html +1178 -10
- nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
- nautobot/project-static/docs/release-notes/index.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
- nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
- nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
- nautobot/project-static/docs/requirements.txt +1 -0
- nautobot/project-static/docs/rest-api/authentication.html +1179 -11
- nautobot/project-static/docs/rest-api/filtering.html +1178 -10
- nautobot/project-static/docs/rest-api/overview.html +1841 -446
- nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +197 -187
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
- nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
- nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
- nautobot/project-static/docs/user-guides/graphql.html +1178 -10
- nautobot/project-static/docs/user-guides/relationships.html +1178 -10
- nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
- nautobot/project-static/js/forms.js +16 -9
- nautobot/project-static/js/theme.js +5 -0
- nautobot/tenancy/api/serializers.py +4 -32
- nautobot/tenancy/api/urls.py +1 -1
- nautobot/tenancy/forms.py +0 -28
- nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
- nautobot/tenancy/models.py +0 -25
- nautobot/tenancy/navigation.py +6 -39
- nautobot/tenancy/templates/tenancy/tenant.html +12 -12
- nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
- nautobot/tenancy/tests/test_api.py +1 -3
- nautobot/tenancy/tests/test_filters.py +10 -5
- nautobot/tenancy/views.py +0 -2
- nautobot/ui/.eslintignore +6 -0
- nautobot/ui/.gitignore +10 -0
- nautobot/ui/.prettierignore +9 -0
- nautobot/ui/.prettierrc +4 -0
- nautobot/ui/README.md +33 -0
- nautobot/ui/app_imports.js.j2 +7 -0
- nautobot/ui/craco.config.js +46 -0
- nautobot/ui/jsconfig-base.json +11 -0
- nautobot/ui/jsconfig.json +5 -0
- nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
- nautobot/ui/package-lock.json +21451 -0
- nautobot/ui/package.json +70 -0
- nautobot/ui/public/index.html +47 -0
- nautobot/ui/public/logo192.png +0 -0
- nautobot/ui/public/logo512.png +0 -0
- nautobot/ui/public/manifest.json +25 -0
- nautobot/ui/public/nautobot_logo.svg +131 -0
- nautobot/ui/public/robots.txt +3 -0
- nautobot/ui/src/App.js +71 -0
- nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
- nautobot/ui/src/components/AppTab.js +40 -0
- nautobot/ui/src/components/Apps.js +60 -0
- nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
- nautobot/ui/src/components/HomePanel.js +58 -0
- nautobot/ui/src/components/JobHistoryTable.js +78 -0
- nautobot/ui/src/components/Layout.js +53 -0
- nautobot/ui/src/components/LoadingWidget.js +25 -0
- nautobot/ui/src/components/Navbar.js +116 -0
- nautobot/ui/src/components/NotificationPopover.js +27 -0
- nautobot/ui/src/components/ObjectListTable.js +209 -0
- nautobot/ui/src/components/ReferenceDataTag.js +35 -0
- nautobot/ui/src/components/RouterButton.js +10 -0
- nautobot/ui/src/components/RouterLink.js +10 -0
- nautobot/ui/src/components/SidebarNav.js +147 -0
- nautobot/ui/src/components/Table.js +48 -0
- nautobot/ui/src/components/TableItem.js +71 -0
- nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
- nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
- nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
- nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
- nautobot/ui/src/components/__tests__/Table.test.js +36 -0
- nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
- nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
- nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
- nautobot/ui/src/components/pagination.js +93 -0
- nautobot/ui/src/components/paginator.js +79 -0
- nautobot/ui/src/components/paginator_form.js +43 -0
- nautobot/ui/src/components/usePagination.js +57 -0
- nautobot/ui/src/constants/apiPath.js +10 -0
- nautobot/ui/src/constants/icons.js +15 -0
- nautobot/ui/src/constants/size.js +15 -0
- nautobot/ui/src/index.js +65 -0
- nautobot/ui/src/reportWebVitals.js +15 -0
- nautobot/ui/src/router.js +77 -0
- nautobot/ui/src/utils/api.js +131 -0
- nautobot/ui/src/utils/app-import.js +15 -0
- nautobot/ui/src/utils/color.js +15 -0
- nautobot/ui/src/utils/date.js +14 -0
- nautobot/ui/src/utils/index.js +15 -0
- nautobot/ui/src/utils/navigation.js +32 -0
- nautobot/ui/src/utils/session.js +64 -0
- nautobot/ui/src/utils/store.js +242 -0
- nautobot/ui/src/utils/string.js +6 -0
- nautobot/ui/src/utils/url.js +4 -0
- nautobot/ui/src/views/Home.js +138 -0
- nautobot/ui/src/views/InstalledApps.js +80 -0
- nautobot/ui/src/views/Login.js +48 -0
- nautobot/ui/src/views/Logout.js +20 -0
- nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
- nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
- nautobot/ui/src/views/__tests__/Login.test.js +15 -0
- nautobot/ui/src/views/generic/GenericView.js +142 -0
- nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
- nautobot/ui/src/views/generic/ObjectList.js +127 -0
- nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
- nautobot/users/admin.py +1 -1
- nautobot/users/api/serializers.py +51 -61
- nautobot/users/api/urls.py +1 -1
- nautobot/users/api/views.py +53 -2
- nautobot/users/tests/test_api.py +110 -25
- nautobot/virtualization/api/serializers.py +18 -130
- nautobot/virtualization/api/urls.py +1 -1
- nautobot/virtualization/api/views.py +1 -22
- nautobot/virtualization/forms.py +13 -99
- nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
- nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
- nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
- nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
- nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
- nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
- nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
- nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
- nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
- nautobot/virtualization/models.py +31 -123
- nautobot/virtualization/navigation.py +18 -99
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
- nautobot/virtualization/tests/test_api.py +25 -26
- nautobot/virtualization/tests/test_filters.py +41 -15
- nautobot/virtualization/tests/test_models.py +31 -7
- nautobot/virtualization/tests/test_views.py +42 -25
- nautobot/virtualization/views.py +7 -6
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
- nautobot/circuits/api/nested_serializers.py +0 -69
- nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
- nautobot/dcim/api/nested_serializers.py +0 -356
- nautobot/dcim/templates/dcim/device_import.html +0 -5
- nautobot/dcim/templates/dcim/device_import_child.html +0 -5
- nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
- nautobot/extras/api/nested_serializers.py +0 -353
- nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
- nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
- nautobot/extras/reports.py +0 -60
- nautobot/extras/scripts.py +0 -72
- nautobot/extras/tests/example_jobs/script_variables.py +0 -67
- nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
- nautobot/extras/tests/example_jobs/test_fail.py +0 -16
- nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
- nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
- nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
- nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
- nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
- nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
- nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
- nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
- nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
- nautobot/extras/tests/example_jobs/test_pass.py +0 -19
- nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
- nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
- nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
- nautobot/ipam/api/nested_serializers.py +0 -159
- nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
- nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
- nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
- nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
- nautobot/tenancy/api/nested_serializers.py +0 -31
- nautobot/users/api/nested_serializers.py +0 -67
- nautobot/virtualization/api/nested_serializers.py +0 -65
- /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
- /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
nautobot/core/views/generic.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from copy import deepcopy
|
|
2
|
+
from io import BytesIO
|
|
2
3
|
import logging
|
|
3
4
|
import re
|
|
4
5
|
|
|
@@ -12,16 +13,18 @@ from django.core.exceptions import (
|
|
|
12
13
|
)
|
|
13
14
|
from django.db import transaction, IntegrityError
|
|
14
15
|
from django.db.models import ManyToManyField, ProtectedError
|
|
15
|
-
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
16
|
-
from django.http import HttpResponse
|
|
16
|
+
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
17
|
+
from django.http import HttpResponse, JsonResponse
|
|
17
18
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
18
|
-
from django.urls import NoReverseMatch, reverse
|
|
19
19
|
from django.utils.html import escape
|
|
20
20
|
from django.utils.http import is_safe_url
|
|
21
21
|
from django.utils.safestring import mark_safe
|
|
22
22
|
from django.views.generic import View
|
|
23
23
|
from django_tables2 import RequestConfig
|
|
24
|
+
from rest_framework.exceptions import ParseError
|
|
24
25
|
|
|
26
|
+
from nautobot.core.api.parsers import NautobotCSVParser
|
|
27
|
+
from nautobot.core.api.utils import get_serializer_for_model
|
|
25
28
|
from nautobot.core.forms import SearchForm
|
|
26
29
|
from nautobot.core.exceptions import AbortTransaction
|
|
27
30
|
from nautobot.core.forms import (
|
|
@@ -36,7 +39,6 @@ from nautobot.core.forms import (
|
|
|
36
39
|
)
|
|
37
40
|
from nautobot.core.forms.forms import DynamicFilterFormSet
|
|
38
41
|
from nautobot.core.templatetags.helpers import bettertitle, validated_viewname
|
|
39
|
-
from nautobot.core.utils.lookup import get_route_for_model
|
|
40
42
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
41
43
|
from nautobot.core.utils.requests import (
|
|
42
44
|
convert_querydict_to_factory_formset_acceptable_querydict,
|
|
@@ -45,9 +47,13 @@ from nautobot.core.utils.requests import (
|
|
|
45
47
|
)
|
|
46
48
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
47
49
|
from nautobot.core.views.mixins import GetReturnURLMixin, ObjectPermissionRequiredMixin
|
|
48
|
-
from nautobot.core.views.utils import
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
from nautobot.core.views.utils import (
|
|
51
|
+
check_filter_for_display,
|
|
52
|
+
get_csv_form_fields_from_serializer_class,
|
|
53
|
+
handle_protectederror,
|
|
54
|
+
prepare_cloned_fields,
|
|
55
|
+
)
|
|
56
|
+
from nautobot.extras.models import ExportTemplate
|
|
51
57
|
from nautobot.extras.utils import remove_prefix_from_cf_key
|
|
52
58
|
|
|
53
59
|
|
|
@@ -61,6 +67,7 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
61
67
|
|
|
62
68
|
queryset = None
|
|
63
69
|
template_name = None
|
|
70
|
+
use_new_ui = True
|
|
64
71
|
|
|
65
72
|
def get_required_permission(self):
|
|
66
73
|
return get_permission_for_model(self.queryset.model, "view")
|
|
@@ -89,55 +96,41 @@ class ObjectView(ObjectPermissionRequiredMixin, View):
|
|
|
89
96
|
"active_tab": request.GET.get("tab", "main"),
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
# 2.0 TODO: Remove this method in 2.0. Can be retrieved from instance itself now
|
|
93
|
-
# instance.get_changelog_url()
|
|
94
|
-
# Only available on models that support changelogs
|
|
95
|
-
def get_changelog_url(self, instance):
|
|
96
|
-
"""Return the changelog URL for a given instance."""
|
|
97
|
-
meta = self.queryset.model._meta
|
|
98
|
-
|
|
99
|
-
# Don't try to generate a changelog_url for an ObjectChange.
|
|
100
|
-
if meta.model_name == "objectchange":
|
|
101
|
-
return None
|
|
102
|
-
|
|
103
|
-
route = get_route_for_model(instance, "changelog")
|
|
104
|
-
|
|
105
|
-
# Iterate the pk-like fields and try to get a URL, or return None.
|
|
106
|
-
fields = ["pk", "slug"]
|
|
107
|
-
for field in fields:
|
|
108
|
-
if not hasattr(instance, field):
|
|
109
|
-
continue
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
return reverse(route, kwargs={field: getattr(instance, field)})
|
|
113
|
-
except NoReverseMatch:
|
|
114
|
-
continue
|
|
115
|
-
|
|
116
|
-
# This object likely doesn't have a changelog route defined.
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
99
|
def get(self, request, *args, **kwargs):
|
|
120
100
|
"""
|
|
121
101
|
Generic GET handler for accessing an object by PK or slug
|
|
122
102
|
"""
|
|
123
103
|
instance = get_object_or_404(self.queryset, **kwargs)
|
|
124
104
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
# TODO: this feels inelegant - should the tabs lookup be a dedicated endpoint rather than piggybacking
|
|
106
|
+
# on the object-retrieve endpoint?
|
|
107
|
+
# TODO: similar functionality probably needed in NautobotUIViewSet as well, not currently present
|
|
108
|
+
if request.GET.get("viewconfig", None) == "true":
|
|
109
|
+
# TODO: we shouldn't be importing a private-named function from another module. Should it be renamed?
|
|
110
|
+
from nautobot.extras.templatetags.plugins import _get_registered_content
|
|
129
111
|
|
|
130
|
-
|
|
131
|
-
request,
|
|
132
|
-
self.get_template_name(),
|
|
133
|
-
{
|
|
112
|
+
temp_fake_context = {
|
|
134
113
|
"object": instance,
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
114
|
+
"request": request,
|
|
115
|
+
"settings": {},
|
|
116
|
+
"csrf_token": "",
|
|
117
|
+
"perms": {},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
plugin_tabs = _get_registered_content(instance, "detail_tabs", temp_fake_context, return_html=False)
|
|
121
|
+
resp = {"tabs": plugin_tabs}
|
|
122
|
+
return JsonResponse(resp)
|
|
123
|
+
else:
|
|
124
|
+
return render(
|
|
125
|
+
request,
|
|
126
|
+
self.get_template_name(),
|
|
127
|
+
{
|
|
128
|
+
"object": instance,
|
|
129
|
+
"verbose_name": self.queryset.model._meta.verbose_name,
|
|
130
|
+
"verbose_name_plural": self.queryset.model._meta.verbose_name_plural,
|
|
131
|
+
**self.get_extra_context(request, instance),
|
|
132
|
+
},
|
|
133
|
+
)
|
|
141
134
|
|
|
142
135
|
|
|
143
136
|
class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
@@ -165,6 +158,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
165
158
|
"per_page", # used by get_paginate_count
|
|
166
159
|
"sort", # table sorting
|
|
167
160
|
)
|
|
161
|
+
use_new_ui = True
|
|
168
162
|
|
|
169
163
|
def get_filter_params(self, request):
|
|
170
164
|
"""Helper function - take request.GET and discard any parameters that are not used for queryset filtering."""
|
|
@@ -174,6 +168,7 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
174
168
|
def get_required_permission(self):
|
|
175
169
|
return get_permission_for_model(self.queryset.model, "view")
|
|
176
170
|
|
|
171
|
+
# TODO: remove this as well?
|
|
177
172
|
def queryset_to_yaml(self):
|
|
178
173
|
"""
|
|
179
174
|
Export the queryset of objects as concatenated YAML documents.
|
|
@@ -182,35 +177,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
182
177
|
|
|
183
178
|
return "---\n".join(yaml_data)
|
|
184
179
|
|
|
185
|
-
def queryset_to_csv(self):
|
|
186
|
-
"""
|
|
187
|
-
Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
|
|
188
|
-
"""
|
|
189
|
-
csv_data = []
|
|
190
|
-
custom_field_keys = []
|
|
191
|
-
|
|
192
|
-
# Start with the column headers
|
|
193
|
-
headers = self.queryset.model.csv_headers.copy()
|
|
194
|
-
|
|
195
|
-
# Add custom field headers, if any
|
|
196
|
-
if hasattr(self.queryset.model, "_custom_field_data"):
|
|
197
|
-
for custom_field in CustomField.objects.get_for_model(self.queryset.model):
|
|
198
|
-
headers.append(custom_field.add_prefix_to_cf_key())
|
|
199
|
-
custom_field_keys.append(custom_field.key)
|
|
200
|
-
|
|
201
|
-
csv_data.append(",".join(headers))
|
|
202
|
-
|
|
203
|
-
# Iterate through the queryset appending each object
|
|
204
|
-
for obj in self.queryset:
|
|
205
|
-
data = obj.to_csv()
|
|
206
|
-
|
|
207
|
-
for custom_field_key in custom_field_keys:
|
|
208
|
-
data += (obj.cf.get(custom_field_key, ""),)
|
|
209
|
-
|
|
210
|
-
csv_data.append(csv_format(data))
|
|
211
|
-
|
|
212
|
-
return "\n".join(csv_data)
|
|
213
|
-
|
|
214
180
|
def validate_action_buttons(self, request):
|
|
215
181
|
"""Verify actions in self.action_buttons are valid view actions."""
|
|
216
182
|
|
|
@@ -286,13 +252,6 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
286
252
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
287
253
|
return response
|
|
288
254
|
|
|
289
|
-
# Fall back to built-in CSV formatting if export requested but no template specified
|
|
290
|
-
elif "export" in request.GET and hasattr(model, "to_csv"):
|
|
291
|
-
response = HttpResponse(self.queryset_to_csv(), content_type="text/csv")
|
|
292
|
-
filename = f"{settings.BRANDING_PREPENDED_FILENAME}{self.queryset.model._meta.verbose_name_plural}.csv"
|
|
293
|
-
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
294
|
-
return response
|
|
295
|
-
|
|
296
255
|
# Provide a hook to tweak the queryset based on the request immediately prior to rendering the object list
|
|
297
256
|
self.queryset = self.alter_queryset(request)
|
|
298
257
|
|
|
@@ -871,31 +830,31 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
871
830
|
Import objects in bulk (CSV format).
|
|
872
831
|
|
|
873
832
|
queryset: Base queryset for the model
|
|
874
|
-
model_form: The form used to create each imported object
|
|
875
833
|
table: The django-tables2 Table used to render the list of imported objects
|
|
876
834
|
template_name: The name of the template
|
|
877
|
-
widget_attrs: A dict of attributes to apply to the import widget (e.g. to require a session key)
|
|
878
835
|
"""
|
|
879
836
|
|
|
880
837
|
queryset = None
|
|
881
|
-
model_form = None
|
|
882
838
|
table = None
|
|
883
839
|
template_name = "generic/object_bulk_import.html"
|
|
884
|
-
|
|
840
|
+
|
|
841
|
+
def __init__(self, *args, **kwargs):
|
|
842
|
+
super().__init__(*args, **kwargs)
|
|
843
|
+
self.serializer_class = get_serializer_for_model(self.queryset.model)
|
|
844
|
+
self.fields = get_csv_form_fields_from_serializer_class(self.serializer_class)
|
|
845
|
+
self.required_field_names = [
|
|
846
|
+
field["name"]
|
|
847
|
+
for field in get_csv_form_fields_from_serializer_class(self.serializer_class)
|
|
848
|
+
if field["required"]
|
|
849
|
+
]
|
|
885
850
|
|
|
886
851
|
def _import_form(self, *args, **kwargs):
|
|
887
852
|
class CSVImportForm(BootstrapMixin, Form):
|
|
888
|
-
csv_data = CSVDataField(
|
|
889
|
-
csv_file = CSVFileField(
|
|
853
|
+
csv_data = CSVDataField(required_field_names=self.required_field_names)
|
|
854
|
+
csv_file = CSVFileField()
|
|
890
855
|
|
|
891
856
|
return CSVImportForm(*args, **kwargs)
|
|
892
857
|
|
|
893
|
-
def _save_obj(self, obj_form, request):
|
|
894
|
-
"""
|
|
895
|
-
Provide a hook to modify the object immediately before saving it (e.g. to encrypt secret data).
|
|
896
|
-
"""
|
|
897
|
-
return obj_form.save()
|
|
898
|
-
|
|
899
858
|
def get_required_permission(self):
|
|
900
859
|
return get_permission_for_model(self.queryset.model, "add")
|
|
901
860
|
|
|
@@ -905,8 +864,8 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
905
864
|
self.template_name,
|
|
906
865
|
{
|
|
907
866
|
"form": self._import_form(),
|
|
908
|
-
"fields": self.
|
|
909
|
-
"obj_type": self.
|
|
867
|
+
"fields": self.fields,
|
|
868
|
+
"obj_type": self.queryset.model._meta.verbose_name,
|
|
910
869
|
"return_url": self.get_return_url(request),
|
|
911
870
|
"active_tab": "csv-data",
|
|
912
871
|
},
|
|
@@ -927,18 +886,24 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
927
886
|
field_name = "csv_file"
|
|
928
887
|
else:
|
|
929
888
|
field_name = "csv_data"
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
889
|
+
csvtext = form.cleaned_data[field_name]
|
|
890
|
+
|
|
891
|
+
try:
|
|
892
|
+
data = NautobotCSVParser().parse(
|
|
893
|
+
stream=BytesIO(csvtext.encode("utf-8")),
|
|
894
|
+
parser_context={"request": request, "serializer_class": self.serializer_class},
|
|
895
|
+
)
|
|
896
|
+
serializer = self.serializer_class(data=data, context={"request": request}, many=True)
|
|
897
|
+
if serializer.is_valid():
|
|
898
|
+
new_objs = serializer.save()
|
|
938
899
|
else:
|
|
939
|
-
for
|
|
940
|
-
|
|
900
|
+
for row, errors in enumerate(serializer.errors, start=1):
|
|
901
|
+
for field, err in errors.items():
|
|
902
|
+
form.add_error(field_name, f"Row {row}: {field}: {err[0]}")
|
|
941
903
|
raise ValidationError("")
|
|
904
|
+
except ParseError as exc:
|
|
905
|
+
form.add_error(None, str(exc))
|
|
906
|
+
raise ValidationError("")
|
|
942
907
|
|
|
943
908
|
# Enforce object-level permissions
|
|
944
909
|
if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
|
|
@@ -977,8 +942,8 @@ class BulkImportView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|
|
977
942
|
self.template_name,
|
|
978
943
|
{
|
|
979
944
|
"form": form,
|
|
980
|
-
"fields": self.
|
|
981
|
-
"obj_type": self.
|
|
945
|
+
"fields": self.fields,
|
|
946
|
+
"obj_type": self.queryset.model._meta.verbose_name,
|
|
982
947
|
"return_url": self.get_return_url(request),
|
|
983
948
|
"active_tab": "csv-file" if form.has_error("csv_file") else "csv-data",
|
|
984
949
|
},
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from io import BytesIO
|
|
1
2
|
import logging
|
|
2
3
|
|
|
3
4
|
from django.contrib import messages
|
|
@@ -11,7 +12,7 @@ from django.core.exceptions import (
|
|
|
11
12
|
)
|
|
12
13
|
from django.db import transaction
|
|
13
14
|
from django.db.models import ManyToManyField, ProtectedError
|
|
14
|
-
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
15
|
+
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput
|
|
15
16
|
from django.http import HttpResponse
|
|
16
17
|
from django.shortcuts import get_object_or_404, redirect
|
|
17
18
|
from django.template.loader import select_template, TemplateDoesNotExist
|
|
@@ -29,7 +30,8 @@ from rest_framework.viewsets import GenericViewSet
|
|
|
29
30
|
|
|
30
31
|
from drf_spectacular.utils import extend_schema
|
|
31
32
|
|
|
32
|
-
from nautobot.core.api.
|
|
33
|
+
from nautobot.core.api.parsers import NautobotCSVParser
|
|
34
|
+
from nautobot.core.api.views import BulkDestroyModelMixin, BulkUpdateModelMixin
|
|
33
35
|
from nautobot.core.forms import (
|
|
34
36
|
BootstrapMixin,
|
|
35
37
|
ConfirmationForm,
|
|
@@ -40,8 +42,12 @@ from nautobot.core.forms import (
|
|
|
40
42
|
from nautobot.core.utils import lookup, permissions
|
|
41
43
|
from nautobot.core.views.renderers import NautobotHTMLRenderer
|
|
42
44
|
from nautobot.core.utils.requests import get_filterable_params_from_filter_params
|
|
43
|
-
from nautobot.core.views.utils import
|
|
44
|
-
|
|
45
|
+
from nautobot.core.views.utils import (
|
|
46
|
+
get_csv_form_fields_from_serializer_class,
|
|
47
|
+
handle_protectederror,
|
|
48
|
+
prepare_cloned_fields,
|
|
49
|
+
)
|
|
50
|
+
from nautobot.extras.models import ExportTemplate
|
|
45
51
|
from nautobot.extras.forms import NoteForm
|
|
46
52
|
from nautobot.extras.tables import ObjectChangeTable, NoteTable
|
|
47
53
|
from nautobot.extras.utils import remove_prefix_from_cf_key
|
|
@@ -179,8 +185,12 @@ class GetReturnURLMixin:
|
|
|
179
185
|
# Note that the use of both `obj.present_in_database` and `obj.pk` is correct here because this conditional
|
|
180
186
|
# handles all three of the create, update, and delete operations. When Django deletes an instance
|
|
181
187
|
# from the DB, it sets the instance's PK field to None, regardless of the use of a UUID.
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
try:
|
|
189
|
+
if obj is not None and obj.present_in_database and obj.pk:
|
|
190
|
+
return obj.get_absolute_url()
|
|
191
|
+
except AttributeError:
|
|
192
|
+
# Model has no get_absolute_url() method or no reverse match
|
|
193
|
+
pass
|
|
184
194
|
|
|
185
195
|
# Fall back to the default URL (if specified) for the view.
|
|
186
196
|
if self.default_return_url is not None:
|
|
@@ -205,7 +215,6 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
205
215
|
|
|
206
216
|
renderer_classes = [NautobotHTMLRenderer]
|
|
207
217
|
logger = logging.getLogger(__name__)
|
|
208
|
-
lookup_field = "slug"
|
|
209
218
|
# Attributes that need to be specified: form_class, queryset, serializer_class, table_class for most mixins.
|
|
210
219
|
# filterset and filter_params will be initialized in filter_queryset() in ObjectListViewMixin
|
|
211
220
|
filter_params = None
|
|
@@ -522,12 +531,15 @@ class NautobotViewSetMixin(GenericViewSet, AccessMixin, GetReturnURLMixin, FormV
|
|
|
522
531
|
if self.action in ["create", "update"]:
|
|
523
532
|
form_class = getattr(self, "form_class", None)
|
|
524
533
|
elif self.action == "bulk_create":
|
|
534
|
+
required_field_names = [
|
|
535
|
+
field["name"]
|
|
536
|
+
for field in get_csv_form_fields_from_serializer_class(self.serializer_class)
|
|
537
|
+
if field["required"]
|
|
538
|
+
]
|
|
525
539
|
|
|
526
540
|
class BulkCreateForm(BootstrapMixin, Form):
|
|
527
|
-
csv_data = CSVDataField(
|
|
528
|
-
|
|
529
|
-
)
|
|
530
|
-
csv_file = CSVFileField(from_form=self.bulk_create_form_class)
|
|
541
|
+
csv_data = CSVDataField(required_field_names=required_field_names)
|
|
542
|
+
csv_file = CSVFileField()
|
|
531
543
|
|
|
532
544
|
form_class = BulkCreateForm
|
|
533
545
|
else:
|
|
@@ -566,6 +578,17 @@ class ObjectDetailViewMixin(NautobotViewSetMixin, mixins.RetrieveModelMixin):
|
|
|
566
578
|
UI mixin to retrieve a model instance.
|
|
567
579
|
"""
|
|
568
580
|
|
|
581
|
+
def retrieve(self, request, *args, **kwargs):
|
|
582
|
+
"""
|
|
583
|
+
Retrieve a model instance.
|
|
584
|
+
"""
|
|
585
|
+
instance = self.get_object()
|
|
586
|
+
serializer = self.get_serializer(instance)
|
|
587
|
+
|
|
588
|
+
context = serializer.data
|
|
589
|
+
context["use_new_ui"] = True
|
|
590
|
+
return Response(context)
|
|
591
|
+
|
|
569
592
|
|
|
570
593
|
class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
571
594
|
"""
|
|
@@ -622,13 +645,6 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
622
645
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
623
646
|
return response
|
|
624
647
|
|
|
625
|
-
# Fall back to built-in CSV formatting if export requested but no template specified
|
|
626
|
-
elif "export" in request.GET and hasattr(model, "to_csv"):
|
|
627
|
-
response = HttpResponse(self.queryset_to_csv(), content_type="text/csv")
|
|
628
|
-
filename = f"nautobot_{queryset.model._meta.verbose_name_plural}.csv"
|
|
629
|
-
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
|
630
|
-
return response
|
|
631
|
-
|
|
632
648
|
return None
|
|
633
649
|
|
|
634
650
|
def queryset_to_yaml(self):
|
|
@@ -640,40 +656,11 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
640
656
|
|
|
641
657
|
return "---\n".join(yaml_data)
|
|
642
658
|
|
|
643
|
-
def queryset_to_csv(self):
|
|
644
|
-
"""
|
|
645
|
-
Export the queryset of objects as comma-separated value (CSV), using the model's to_csv() method.
|
|
646
|
-
"""
|
|
647
|
-
queryset = self.filter_queryset(self.get_queryset())
|
|
648
|
-
csv_data = []
|
|
649
|
-
custom_fields = []
|
|
650
|
-
# Start with the column headers
|
|
651
|
-
headers = queryset.model.csv_headers.copy()
|
|
652
|
-
|
|
653
|
-
# Add custom field headers, if any
|
|
654
|
-
if hasattr(queryset.model, "_custom_field_data"):
|
|
655
|
-
for custom_field in CustomField.objects.get_for_model(queryset.model):
|
|
656
|
-
headers.append(custom_field.add_prefix_to_cf_key())
|
|
657
|
-
custom_fields.append(custom_field.key)
|
|
658
|
-
|
|
659
|
-
csv_data.append(",".join(headers))
|
|
660
|
-
|
|
661
|
-
# Iterate through the queryset appending each object
|
|
662
|
-
for obj in queryset:
|
|
663
|
-
data = obj.to_csv()
|
|
664
|
-
|
|
665
|
-
for custom_field in custom_fields:
|
|
666
|
-
data += (obj.cf.get(custom_field, ""),)
|
|
667
|
-
|
|
668
|
-
csv_data.append(csv_format(data))
|
|
669
|
-
|
|
670
|
-
return "\n".join(csv_data)
|
|
671
|
-
|
|
672
659
|
def list(self, request, *args, **kwargs):
|
|
673
660
|
"""
|
|
674
661
|
List the model instances.
|
|
675
662
|
"""
|
|
676
|
-
context = {}
|
|
663
|
+
context = {"use_new_ui": True}
|
|
677
664
|
if "export" in request.GET:
|
|
678
665
|
queryset = self.get_queryset()
|
|
679
666
|
model = queryset.model
|
|
@@ -895,14 +882,12 @@ class ObjectBulkDestroyViewMixin(NautobotViewSetMixin, BulkDestroyModelMixin):
|
|
|
895
882
|
return Response(data)
|
|
896
883
|
|
|
897
884
|
|
|
898
|
-
class ObjectBulkCreateViewMixin(NautobotViewSetMixin
|
|
885
|
+
class ObjectBulkCreateViewMixin(NautobotViewSetMixin):
|
|
899
886
|
"""
|
|
900
887
|
UI mixin to bulk create model instances.
|
|
901
888
|
"""
|
|
902
889
|
|
|
903
890
|
bulk_create_active_tab = "csv-data"
|
|
904
|
-
bulk_create_form_class = None
|
|
905
|
-
bulk_create_widget_attrs = {}
|
|
906
891
|
|
|
907
892
|
def _process_bulk_create_form(self, form):
|
|
908
893
|
# Iterate through CSV data and bind each row to a new model form instance.
|
|
@@ -918,18 +903,24 @@ class ObjectBulkCreateViewMixin(NautobotViewSetMixin, BulkCreateModelMixin):
|
|
|
918
903
|
self.bulk_create_active_tab = "csv-file"
|
|
919
904
|
else:
|
|
920
905
|
field_name = "csv_data"
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
906
|
+
|
|
907
|
+
csvtext = form.cleaned_data[field_name]
|
|
908
|
+
try:
|
|
909
|
+
data = NautobotCSVParser().parse(
|
|
910
|
+
stream=BytesIO(csvtext.encode("utf-8")),
|
|
911
|
+
parser_context={"request": request, "serializer_class": self.serializer_class},
|
|
912
|
+
)
|
|
913
|
+
serializer = self.serializer_class(data=data, context={"request": request}, many=True)
|
|
914
|
+
if serializer.is_valid():
|
|
915
|
+
new_objs = serializer.save()
|
|
929
916
|
else:
|
|
930
|
-
for
|
|
931
|
-
|
|
917
|
+
for row, errors in enumerate(serializer.errors, start=1):
|
|
918
|
+
for field, err in errors.items():
|
|
919
|
+
form.add_error(field_name, f"Row {row}: {field}: {err[0]}")
|
|
932
920
|
raise ValidationError("")
|
|
921
|
+
except exceptions.ParseError as exc:
|
|
922
|
+
form.add_error(None, str(exc))
|
|
923
|
+
raise ValidationError("")
|
|
933
924
|
|
|
934
925
|
# Enforce object-level permissions
|
|
935
926
|
if queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
|
nautobot/core/views/renderers.py
CHANGED
|
@@ -19,8 +19,8 @@ from nautobot.core.utils.requests import (
|
|
|
19
19
|
normalize_querydict,
|
|
20
20
|
)
|
|
21
21
|
from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
|
|
22
|
-
from nautobot.core.views.utils import check_filter_for_display
|
|
23
|
-
from nautobot.extras.models.change_logging import
|
|
22
|
+
from nautobot.core.views.utils import check_filter_for_display, get_csv_form_fields_from_serializer_class
|
|
23
|
+
from nautobot.extras.models.change_logging import ObjectChange
|
|
24
24
|
from nautobot.extras.utils import get_base_template
|
|
25
25
|
|
|
26
26
|
|
|
@@ -136,7 +136,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
136
136
|
form = None
|
|
137
137
|
table = None
|
|
138
138
|
search_form = None
|
|
139
|
-
changelog_url = None
|
|
140
139
|
instance = None
|
|
141
140
|
filter_form = None
|
|
142
141
|
queryset = view.alter_queryset(request)
|
|
@@ -146,8 +145,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
146
145
|
if view.action in ["create", "retrieve", "update", "destroy", "changelog", "notes"]:
|
|
147
146
|
instance = view.get_object()
|
|
148
147
|
return_url = view.get_return_url(request, instance)
|
|
149
|
-
if isinstance(instance, ChangeLoggedModel):
|
|
150
|
-
changelog_url = instance.get_changelog_url()
|
|
151
148
|
else:
|
|
152
149
|
return_url = view.get_return_url(request)
|
|
153
150
|
# Get form for context rendering according to view.action unless it is previously set.
|
|
@@ -208,7 +205,6 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
208
205
|
table = self.construct_table(view, object=instance, content_type=content_type)
|
|
209
206
|
|
|
210
207
|
context = {
|
|
211
|
-
"changelog_url": changelog_url, # NOTE: This context key is deprecated in favor of `object.get_changelog_url`.
|
|
212
208
|
"content_type": content_type,
|
|
213
209
|
"form": form,
|
|
214
210
|
"filter_form": filter_form,
|
|
@@ -249,7 +245,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
249
245
|
context.update(
|
|
250
246
|
{
|
|
251
247
|
"active_tab": view.bulk_create_active_tab if view.bulk_create_active_tab else "csv-data",
|
|
252
|
-
"fields": view.
|
|
248
|
+
"fields": get_csv_form_fields_from_serializer_class(view.serializer_class),
|
|
253
249
|
}
|
|
254
250
|
)
|
|
255
251
|
elif view.action in ["changelog", "notes"]:
|
|
@@ -270,4 +266,7 @@ class NautobotHTMLRenderer(renderers.BrowsableAPIRenderer):
|
|
|
270
266
|
# Get the corresponding template based on self.action in view.get_template_name() unless it is already specified in the Response() data.
|
|
271
267
|
# See form_valid() for self.action == "bulk_create".
|
|
272
268
|
self.template = data.get("template", view.get_template_name())
|
|
269
|
+
|
|
270
|
+
# NautobotUIViewSets pass "use_new_ui" in context as they share the same class and are just different methods
|
|
271
|
+
self.use_new_ui = data.get("use_new_ui", False)
|
|
273
272
|
return super().render(data, accepted_media_type=accepted_media_type, renderer_context=renderer_context)
|
nautobot/core/views/utils.py
CHANGED
|
@@ -6,6 +6,9 @@ from django.db.models import ForeignKey
|
|
|
6
6
|
from django.utils.html import escape
|
|
7
7
|
from django.utils.safestring import mark_safe
|
|
8
8
|
|
|
9
|
+
from rest_framework import serializers
|
|
10
|
+
|
|
11
|
+
from nautobot.core.api.fields import ChoiceField, ContentTypeField, TimeZoneSerializerField
|
|
9
12
|
from nautobot.core.models.utils import is_taggable
|
|
10
13
|
from nautobot.core.utils.data import is_uuid
|
|
11
14
|
from nautobot.core.utils.filtering import get_filter_field_label
|
|
@@ -56,9 +59,14 @@ def check_filter_for_display(filters, field_name, values):
|
|
|
56
59
|
return resolved_filter
|
|
57
60
|
|
|
58
61
|
|
|
62
|
+
# 2.2 TODO: remove this method as it's no longer used in core.
|
|
59
63
|
def csv_format(data):
|
|
60
64
|
"""
|
|
65
|
+
Convert the given list of data to a CSV row string.
|
|
66
|
+
|
|
61
67
|
Encapsulate any data which contains a comma within double quotes.
|
|
68
|
+
|
|
69
|
+
Obsolete, as CSV rendering in Nautobot core is now handled by nautobot.core.api.renderers.NautobotCSVRenderer.
|
|
62
70
|
"""
|
|
63
71
|
csv = []
|
|
64
72
|
for value in data:
|
|
@@ -85,6 +93,78 @@ def csv_format(data):
|
|
|
85
93
|
return ",".join(csv)
|
|
86
94
|
|
|
87
95
|
|
|
96
|
+
def get_csv_form_fields_from_serializer_class(serializer_class):
|
|
97
|
+
"""From the given serializer class, build a list of field dicts suitable for rendering in the CSV import form."""
|
|
98
|
+
serializer = serializer_class(context={"request": None, "depth": 0})
|
|
99
|
+
fields = []
|
|
100
|
+
for field_name, field in serializer.fields.items():
|
|
101
|
+
if field.read_only:
|
|
102
|
+
continue
|
|
103
|
+
if field_name == "custom_fields":
|
|
104
|
+
from nautobot.extras.choices import CustomFieldTypeChoices
|
|
105
|
+
from nautobot.extras.models import CustomField
|
|
106
|
+
|
|
107
|
+
cfs = CustomField.objects.get_for_model(serializer_class.Meta.model)
|
|
108
|
+
for cf in cfs:
|
|
109
|
+
cf_form_field = cf.to_form_field(set_initial=False)
|
|
110
|
+
field_info = {
|
|
111
|
+
"name": cf.add_prefix_to_cf_key(),
|
|
112
|
+
"required": cf_form_field.required,
|
|
113
|
+
"label": cf_form_field.label,
|
|
114
|
+
"help_text": cf_form_field.help_text,
|
|
115
|
+
}
|
|
116
|
+
if cf.type == CustomFieldTypeChoices.TYPE_BOOLEAN:
|
|
117
|
+
field_info["format"] = mark_safe("<code>true</code> or <code>false</code>")
|
|
118
|
+
elif cf.type == CustomFieldTypeChoices.TYPE_DATE:
|
|
119
|
+
field_info["format"] = mark_safe("<code>YYYY-MM-DD</code>")
|
|
120
|
+
elif cf.type == CustomFieldTypeChoices.TYPE_SELECT:
|
|
121
|
+
field_info["choices"] = {cfc.value: cfc.value for cfc in cf.custom_field_choices.all()}
|
|
122
|
+
elif cf.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
|
|
123
|
+
field_info["format"] = mark_safe('<code>"value,value"</code>')
|
|
124
|
+
field_info["choices"] = {cfc.value: cfc.value for cfc in cf.custom_field_choices.all()}
|
|
125
|
+
fields.append(field_info)
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
field_info = {
|
|
129
|
+
"name": field_name,
|
|
130
|
+
"required": field.required,
|
|
131
|
+
"label": field.label,
|
|
132
|
+
"help_text": field.help_text,
|
|
133
|
+
}
|
|
134
|
+
if isinstance(field, serializers.BooleanField):
|
|
135
|
+
field_info["format"] = mark_safe("<code>true</code> or <code>false</code>")
|
|
136
|
+
elif isinstance(field, serializers.DateField):
|
|
137
|
+
field_info["format"] = mark_safe("<code>YYYY-MM-DD</code>")
|
|
138
|
+
elif isinstance(field, TimeZoneSerializerField):
|
|
139
|
+
field_info["format"] = mark_safe(
|
|
140
|
+
'<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">available options</a>'
|
|
141
|
+
)
|
|
142
|
+
elif isinstance(field, serializers.ManyRelatedField):
|
|
143
|
+
if isinstance(field.child_relation, ContentTypeField):
|
|
144
|
+
field_info["format"] = mark_safe('<code>"app_label.model,app_label.model"</code>')
|
|
145
|
+
else:
|
|
146
|
+
field_info["format"] = mark_safe('<code>"natural_key_slug,natural_key_slug"</code>')
|
|
147
|
+
elif isinstance(field, serializers.RelatedField):
|
|
148
|
+
if isinstance(field, ContentTypeField):
|
|
149
|
+
field_info["format"] = mark_safe("<code>app_label.model</code>")
|
|
150
|
+
else:
|
|
151
|
+
field_info["format"] = mark_safe("<code>natural_key_slug</code>")
|
|
152
|
+
elif isinstance(field, (serializers.ListField, serializers.MultipleChoiceField)):
|
|
153
|
+
field_info["format"] = mark_safe('<code>"value,value"</code>')
|
|
154
|
+
elif isinstance(field, (serializers.DictField, serializers.JSONField)):
|
|
155
|
+
pass # Not trivial to specify a format as it could be a JSON dict or a comma-separated string
|
|
156
|
+
|
|
157
|
+
if isinstance(field, ChoiceField):
|
|
158
|
+
field_info["choices"] = field.choices
|
|
159
|
+
|
|
160
|
+
fields.append(field_info)
|
|
161
|
+
|
|
162
|
+
# Move all required fields to the start of the list
|
|
163
|
+
# TODO this ordering should be defined by the serializer instead...
|
|
164
|
+
fields = sorted(fields, key=lambda info: 1 if info["required"] else 2)
|
|
165
|
+
return fields
|
|
166
|
+
|
|
167
|
+
|
|
88
168
|
def handle_protectederror(obj_list, request, e):
|
|
89
169
|
"""
|
|
90
170
|
Generate a user-friendly error message in response to a ProtectedError exception.
|