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/ipam/models.py
CHANGED
|
@@ -7,7 +7,6 @@ from django.core.exceptions import ValidationError, MultipleObjectsReturned
|
|
|
7
7
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
8
8
|
from django.db import models
|
|
9
9
|
from django.db.models import Q
|
|
10
|
-
from django.urls import reverse
|
|
11
10
|
from django.utils.functional import cached_property, classproperty
|
|
12
11
|
|
|
13
12
|
from nautobot.core.models import BaseManager, BaseModel
|
|
@@ -15,11 +14,11 @@ from nautobot.core.models.fields import AutoSlugField, JSONArrayField
|
|
|
15
14
|
from nautobot.core.models.generics import OrganizationalModel, PrimaryModel
|
|
16
15
|
from nautobot.core.models.utils import array_to_string
|
|
17
16
|
from nautobot.core.utils.data import UtilizationData
|
|
18
|
-
from nautobot.dcim.models import
|
|
19
|
-
from nautobot.extras.models import
|
|
17
|
+
from nautobot.dcim.models import Interface
|
|
18
|
+
from nautobot.extras.models import RoleField, Status, StatusField
|
|
20
19
|
from nautobot.extras.utils import extras_features
|
|
21
20
|
from nautobot.ipam import choices
|
|
22
|
-
from nautobot.virtualization.models import
|
|
21
|
+
from nautobot.virtualization.models import VMInterface
|
|
23
22
|
from .constants import (
|
|
24
23
|
SERVICE_PORT_MAX,
|
|
25
24
|
SERVICE_PORT_MIN,
|
|
@@ -67,15 +66,17 @@ class Namespace(PrimaryModel):
|
|
|
67
66
|
null=True,
|
|
68
67
|
)
|
|
69
68
|
|
|
69
|
+
@property
|
|
70
|
+
def ip_addresses(self):
|
|
71
|
+
"""Return all IPAddresses associated to this Namespace through their parent Prefix."""
|
|
72
|
+
return IPAddress.objects.filter(parent__namespace=self).distinct()
|
|
73
|
+
|
|
70
74
|
class Meta:
|
|
71
75
|
ordering = ("name",)
|
|
72
76
|
|
|
73
77
|
def __str__(self):
|
|
74
78
|
return self.name
|
|
75
79
|
|
|
76
|
-
def get_absolute_url(self):
|
|
77
|
-
return reverse("ipam:namespace", args=[self.pk])
|
|
78
|
-
|
|
79
80
|
|
|
80
81
|
def get_default_namespace():
|
|
81
82
|
"""Return the Global namespace for use in default value for foreign keys."""
|
|
@@ -120,6 +121,12 @@ class VRF(PrimaryModel):
|
|
|
120
121
|
through="ipam.VRFDeviceAssignment",
|
|
121
122
|
through_fields=("vrf", "device"),
|
|
122
123
|
)
|
|
124
|
+
virtual_machines = models.ManyToManyField(
|
|
125
|
+
to="virtualization.VirtualMachine",
|
|
126
|
+
related_name="vrfs",
|
|
127
|
+
through="ipam.VRFDeviceAssignment",
|
|
128
|
+
through_fields=("vrf", "virtual_machine"),
|
|
129
|
+
)
|
|
123
130
|
prefixes = models.ManyToManyField(
|
|
124
131
|
to="ipam.Prefix",
|
|
125
132
|
related_name="vrfs",
|
|
@@ -136,14 +143,13 @@ class VRF(PrimaryModel):
|
|
|
136
143
|
import_targets = models.ManyToManyField(to="ipam.RouteTarget", related_name="importing_vrfs", blank=True)
|
|
137
144
|
export_targets = models.ManyToManyField(to="ipam.RouteTarget", related_name="exporting_vrfs", blank=True)
|
|
138
145
|
|
|
139
|
-
csv_headers = ["name", "rd", "tenant", "description"]
|
|
140
146
|
clone_fields = [
|
|
141
147
|
"tenant",
|
|
142
148
|
"description",
|
|
143
149
|
]
|
|
144
150
|
|
|
145
151
|
class Meta:
|
|
146
|
-
ordering = ("namespace", "name") # (name, rd) may be non-unique
|
|
152
|
+
ordering = ("namespace", "name", "rd") # (name, rd) may be non-unique
|
|
147
153
|
unique_together = [
|
|
148
154
|
["namespace", "name"],
|
|
149
155
|
["namespace", "rd"],
|
|
@@ -154,17 +160,6 @@ class VRF(PrimaryModel):
|
|
|
154
160
|
def __str__(self):
|
|
155
161
|
return self.display or super().__str__()
|
|
156
162
|
|
|
157
|
-
def get_absolute_url(self):
|
|
158
|
-
return reverse("ipam:vrf", args=[self.pk])
|
|
159
|
-
|
|
160
|
-
def to_csv(self):
|
|
161
|
-
return (
|
|
162
|
-
self.name,
|
|
163
|
-
self.rd,
|
|
164
|
-
self.tenant.name if self.tenant else None,
|
|
165
|
-
self.description,
|
|
166
|
-
)
|
|
167
|
-
|
|
168
163
|
@property
|
|
169
164
|
def display(self):
|
|
170
165
|
if self.namespace:
|
|
@@ -202,6 +197,37 @@ class VRF(PrimaryModel):
|
|
|
202
197
|
instance = self.devices.through.objects.get(vrf=self, device=device)
|
|
203
198
|
return instance.delete()
|
|
204
199
|
|
|
200
|
+
def add_virtual_machine(self, virtual_machine, rd="", name=""):
|
|
201
|
+
"""
|
|
202
|
+
Add a `virtual_machine` to this VRF, optionally overloading `rd` and `name`.
|
|
203
|
+
|
|
204
|
+
If `rd` or `name` are not provided, the values from this VRF will be inherited.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
virtual_machine (VirtualMachine): VirtualMachine instance
|
|
208
|
+
rd (str): (Optional) RD of the VRF when associated with this VirtualMachine
|
|
209
|
+
name (str): (Optional) Name of the VRF when associated with this VirtualMachine
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
VRFDeviceAssignment instance
|
|
213
|
+
"""
|
|
214
|
+
instance = self.virtual_machines.through(vrf=self, virtual_machine=virtual_machine, rd=rd, name=name)
|
|
215
|
+
instance.validated_save()
|
|
216
|
+
return instance
|
|
217
|
+
|
|
218
|
+
def remove_virtual_machine(self, virtual_machine):
|
|
219
|
+
"""
|
|
220
|
+
Remove a `virtual_machine` from this VRF.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
virtual_machine (VirtualMachine): VirtualMachine instance
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
tuple (int, dict): Number of objects deleted and a dict with number of deletions.
|
|
227
|
+
"""
|
|
228
|
+
instance = self.virtual_machines.through.objects.get(vrf=self, virtual_machine=virtual_machine)
|
|
229
|
+
return instance.delete()
|
|
230
|
+
|
|
205
231
|
def add_prefix(self, prefix):
|
|
206
232
|
"""
|
|
207
233
|
Add a `prefix` to this VRF. Each object must be in the same Namespace.
|
|
@@ -232,7 +258,12 @@ class VRF(PrimaryModel):
|
|
|
232
258
|
|
|
233
259
|
class VRFDeviceAssignment(BaseModel):
|
|
234
260
|
vrf = models.ForeignKey("ipam.VRF", on_delete=models.CASCADE, related_name="device_assignments")
|
|
235
|
-
device = models.ForeignKey(
|
|
261
|
+
device = models.ForeignKey(
|
|
262
|
+
"dcim.Device", null=True, blank=True, on_delete=models.CASCADE, related_name="vrf_assignments"
|
|
263
|
+
)
|
|
264
|
+
virtual_machine = models.ForeignKey(
|
|
265
|
+
"virtualization.VirtualMachine", null=True, blank=True, on_delete=models.CASCADE, related_name="vrf_assignments"
|
|
266
|
+
)
|
|
236
267
|
rd = models.CharField(
|
|
237
268
|
max_length=VRF_RD_MAX_LENGTH,
|
|
238
269
|
blank=True,
|
|
@@ -245,11 +276,14 @@ class VRFDeviceAssignment(BaseModel):
|
|
|
245
276
|
class Meta:
|
|
246
277
|
unique_together = [
|
|
247
278
|
["vrf", "device"],
|
|
279
|
+
["vrf", "virtual_machine"],
|
|
248
280
|
["device", "rd", "name"],
|
|
281
|
+
["virtual_machine", "rd", "name"],
|
|
249
282
|
]
|
|
250
283
|
|
|
251
284
|
def __str__(self):
|
|
252
|
-
|
|
285
|
+
obj = self.device or self.virtual_machine
|
|
286
|
+
return f"{self.vrf} [{obj}] (rd: {self.rd}, name: {self.name})"
|
|
253
287
|
|
|
254
288
|
def clean(self):
|
|
255
289
|
super().clean()
|
|
@@ -262,6 +296,12 @@ class VRFDeviceAssignment(BaseModel):
|
|
|
262
296
|
if not self.name:
|
|
263
297
|
self.name = self.vrf.name
|
|
264
298
|
|
|
299
|
+
# A VRF must belong to a Device *or* to a VirtualMachine.
|
|
300
|
+
if all([self.device, self.virtual_machine]):
|
|
301
|
+
raise ValidationError("A VRF cannot be associated with both a device and a virtual machine.")
|
|
302
|
+
if not any([self.device, self.virtual_machine]):
|
|
303
|
+
raise ValidationError("A VRF must be associated with either a device or a virtual machine.")
|
|
304
|
+
|
|
265
305
|
|
|
266
306
|
class VRFPrefixAssignment(BaseModel):
|
|
267
307
|
vrf = models.ForeignKey("ipam.VRF", on_delete=models.CASCADE, related_name="+")
|
|
@@ -306,24 +346,12 @@ class RouteTarget(PrimaryModel):
|
|
|
306
346
|
null=True,
|
|
307
347
|
)
|
|
308
348
|
|
|
309
|
-
csv_headers = ["name", "description", "tenant"]
|
|
310
|
-
|
|
311
349
|
class Meta:
|
|
312
350
|
ordering = ["name"]
|
|
313
351
|
|
|
314
352
|
def __str__(self):
|
|
315
353
|
return self.name
|
|
316
354
|
|
|
317
|
-
def get_absolute_url(self):
|
|
318
|
-
return reverse("ipam:routetarget", args=[self.pk])
|
|
319
|
-
|
|
320
|
-
def to_csv(self):
|
|
321
|
-
return (
|
|
322
|
-
self.name,
|
|
323
|
-
self.description,
|
|
324
|
-
self.tenant.name if self.tenant else None,
|
|
325
|
-
)
|
|
326
|
-
|
|
327
355
|
|
|
328
356
|
@extras_features(
|
|
329
357
|
"custom_validators",
|
|
@@ -343,8 +371,6 @@ class RIR(OrganizationalModel):
|
|
|
343
371
|
)
|
|
344
372
|
description = models.CharField(max_length=200, blank=True)
|
|
345
373
|
|
|
346
|
-
csv_headers = ["name", "is_private", "description"]
|
|
347
|
-
|
|
348
374
|
objects = BaseManager.from_queryset(RIRQuerySet)()
|
|
349
375
|
|
|
350
376
|
class Meta:
|
|
@@ -355,16 +381,6 @@ class RIR(OrganizationalModel):
|
|
|
355
381
|
def __str__(self):
|
|
356
382
|
return self.name
|
|
357
383
|
|
|
358
|
-
def get_absolute_url(self):
|
|
359
|
-
return reverse("ipam:rir", args=[self.pk])
|
|
360
|
-
|
|
361
|
-
def to_csv(self):
|
|
362
|
-
return (
|
|
363
|
-
self.name,
|
|
364
|
-
str(self.is_private),
|
|
365
|
-
self.description,
|
|
366
|
-
)
|
|
367
|
-
|
|
368
384
|
|
|
369
385
|
@extras_features(
|
|
370
386
|
"custom_links",
|
|
@@ -376,7 +392,7 @@ class RIR(OrganizationalModel):
|
|
|
376
392
|
"statuses",
|
|
377
393
|
"webhooks",
|
|
378
394
|
)
|
|
379
|
-
class Prefix(PrimaryModel
|
|
395
|
+
class Prefix(PrimaryModel):
|
|
380
396
|
"""
|
|
381
397
|
A Prefix represents an IPv4 or IPv6 network, including mask length.
|
|
382
398
|
Prefixes can optionally be assigned to Locations and VRFs.
|
|
@@ -397,6 +413,8 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
397
413
|
choices=choices.PrefixTypeChoices,
|
|
398
414
|
default=choices.PrefixTypeChoices.TYPE_NETWORK,
|
|
399
415
|
)
|
|
416
|
+
status = StatusField(blank=False, null=False)
|
|
417
|
+
role = RoleField(blank=True, null=True)
|
|
400
418
|
parent = models.ForeignKey(
|
|
401
419
|
"self",
|
|
402
420
|
blank=True,
|
|
@@ -458,20 +476,6 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
458
476
|
|
|
459
477
|
objects = BaseManager.from_queryset(PrefixQuerySet)()
|
|
460
478
|
|
|
461
|
-
csv_headers = [
|
|
462
|
-
"prefix",
|
|
463
|
-
"namespace",
|
|
464
|
-
"type",
|
|
465
|
-
"tenant",
|
|
466
|
-
"location",
|
|
467
|
-
"vlan_group",
|
|
468
|
-
"vlan",
|
|
469
|
-
"status",
|
|
470
|
-
"role",
|
|
471
|
-
"rir",
|
|
472
|
-
"date_allocated",
|
|
473
|
-
"description",
|
|
474
|
-
]
|
|
475
479
|
clone_fields = [
|
|
476
480
|
"date_allocated",
|
|
477
481
|
"description",
|
|
@@ -540,9 +544,6 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
540
544
|
self.prefix_length = prefix.prefixlen
|
|
541
545
|
self.ip_version = prefix.version
|
|
542
546
|
|
|
543
|
-
def get_absolute_url(self):
|
|
544
|
-
return reverse("ipam:prefix", args=[self.pk])
|
|
545
|
-
|
|
546
547
|
# TODO: this function is completely unused at present - remove?
|
|
547
548
|
def get_duplicates(self):
|
|
548
549
|
return Prefix.objects.net_equals(self.prefix).filter(namespace=self.namespace).exclude(pk=self.pk)
|
|
@@ -606,22 +607,6 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
606
607
|
# Determine the child IPs and reparent them to this prefix.
|
|
607
608
|
self.reparent_ips()
|
|
608
609
|
|
|
609
|
-
def to_csv(self):
|
|
610
|
-
return (
|
|
611
|
-
self.prefix,
|
|
612
|
-
self.namespace.name if self.namespace else None,
|
|
613
|
-
self.get_type_display(),
|
|
614
|
-
self.tenant.name if self.tenant else None,
|
|
615
|
-
self.location.name if self.location else None,
|
|
616
|
-
self.vlan.vlan_group.name if self.vlan and self.vlan.vlan_group else None,
|
|
617
|
-
self.vlan.vid if self.vlan else None,
|
|
618
|
-
self.get_status_display(),
|
|
619
|
-
self.role.name if self.role else None,
|
|
620
|
-
self.rir.name if self.rir else None,
|
|
621
|
-
str(self.date_allocated),
|
|
622
|
-
self.description,
|
|
623
|
-
)
|
|
624
|
-
|
|
625
610
|
@property
|
|
626
611
|
def cidr_str(self):
|
|
627
612
|
if self.network is not None and self.prefix_length is not None:
|
|
@@ -660,7 +645,10 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
660
645
|
def reparent_ips(self):
|
|
661
646
|
"""Determine the list of child IPAddresses and set the parent to self."""
|
|
662
647
|
query = IPAddress.objects.select_for_update().filter(
|
|
648
|
+
ip_version=self.ip_version,
|
|
663
649
|
parent_id=self.parent_id,
|
|
650
|
+
host__gte=self.network,
|
|
651
|
+
host__lte=self.broadcast,
|
|
664
652
|
)
|
|
665
653
|
|
|
666
654
|
return query.update(parent=self)
|
|
@@ -791,10 +779,6 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
791
779
|
|
|
792
780
|
return query
|
|
793
781
|
|
|
794
|
-
def get_child_ips(self):
|
|
795
|
-
"""Return all IPAddresses directly contained within this Prefix and Namespace."""
|
|
796
|
-
return self.ip_addresses.all()
|
|
797
|
-
|
|
798
782
|
def get_available_prefixes(self):
|
|
799
783
|
"""
|
|
800
784
|
Return all available Prefixes within this prefix as an IPSet.
|
|
@@ -810,7 +794,7 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
810
794
|
Return all available IPs within this prefix as an IPSet.
|
|
811
795
|
"""
|
|
812
796
|
prefix = netaddr.IPSet(self.prefix)
|
|
813
|
-
child_ips = netaddr.IPSet([ip.address.ip for ip in self.
|
|
797
|
+
child_ips = netaddr.IPSet([ip.address.ip for ip in self.ip_addresses.all()])
|
|
814
798
|
available_ips = prefix - child_ips
|
|
815
799
|
|
|
816
800
|
# IPv6, pool, or IPv4 /31-32 sets are fully usable
|
|
@@ -886,7 +870,7 @@ class Prefix(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
886
870
|
"statuses",
|
|
887
871
|
"webhooks",
|
|
888
872
|
)
|
|
889
|
-
class IPAddress(PrimaryModel
|
|
873
|
+
class IPAddress(PrimaryModel):
|
|
890
874
|
"""
|
|
891
875
|
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
|
892
876
|
configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
|
|
@@ -903,8 +887,9 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
903
887
|
db_index=True,
|
|
904
888
|
help_text="IPv4 or IPv6 host address",
|
|
905
889
|
)
|
|
906
|
-
|
|
907
|
-
|
|
890
|
+
mask_length = models.IntegerField(null=False, db_index=True, help_text="Length of the network mask, in bits.")
|
|
891
|
+
status = StatusField(blank=False, null=False)
|
|
892
|
+
role = RoleField(blank=True, null=True)
|
|
908
893
|
parent = models.ForeignKey(
|
|
909
894
|
"ipam.Prefix",
|
|
910
895
|
blank=True,
|
|
@@ -913,7 +898,7 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
913
898
|
on_delete=models.PROTECT,
|
|
914
899
|
help_text="The parent Prefix of this IPAddress.",
|
|
915
900
|
)
|
|
916
|
-
# ip_version is set internally just like network,
|
|
901
|
+
# ip_version is set internally just like network, and mask_length.
|
|
917
902
|
ip_version = models.IntegerField(
|
|
918
903
|
choices=choices.IPAddressVersionChoices,
|
|
919
904
|
null=True,
|
|
@@ -946,15 +931,6 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
946
931
|
)
|
|
947
932
|
description = models.CharField(max_length=200, blank=True)
|
|
948
933
|
|
|
949
|
-
csv_headers = [
|
|
950
|
-
"address",
|
|
951
|
-
"tenant",
|
|
952
|
-
"status",
|
|
953
|
-
"role",
|
|
954
|
-
"is_primary",
|
|
955
|
-
"dns_name",
|
|
956
|
-
"description",
|
|
957
|
-
]
|
|
958
934
|
clone_fields = [
|
|
959
935
|
"tenant",
|
|
960
936
|
"status",
|
|
@@ -966,7 +942,7 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
966
942
|
objects = BaseManager.from_queryset(IPAddressQuerySet)()
|
|
967
943
|
|
|
968
944
|
class Meta:
|
|
969
|
-
ordering = ("ip_version", "host", "
|
|
945
|
+
ordering = ("ip_version", "host", "mask_length") # address may be non-unique
|
|
970
946
|
verbose_name = "IP address"
|
|
971
947
|
verbose_name_plural = "IP addresses"
|
|
972
948
|
unique_together = ["parent", "host"]
|
|
@@ -991,22 +967,15 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
991
967
|
if address:
|
|
992
968
|
if isinstance(address, str):
|
|
993
969
|
address = netaddr.IPNetwork(address)
|
|
994
|
-
# Note that our "broadcast" field is actually the last IP address in this network.
|
|
995
|
-
# This is different from the more accurate technical meaning of a network's broadcast address in 2 cases:
|
|
996
|
-
# 1. For a point-to-point address (IPv4 /31 or IPv6 /127), there are two addresses in the network,
|
|
997
|
-
# and neither one is considered a broadcast address. We store the second address as our "broadcast".
|
|
998
|
-
# 2. For a host prefix (IPv6 /32 or IPv6 /128) there's only one address in the network.
|
|
999
|
-
# We store this address as both the host and the "broadcast".
|
|
1000
|
-
# This variance is intentional in both cases as we use the "broadcast" primarily for filtering and grouping
|
|
1001
|
-
# of addresses and prefixes, not for packet forwarding. :-)
|
|
1002
|
-
broadcast = address.broadcast if address.broadcast else address[-1]
|
|
1003
970
|
self.host = str(address.ip)
|
|
1004
|
-
self.
|
|
1005
|
-
self.prefix_length = address.prefixlen
|
|
971
|
+
self.mask_length = address.prefixlen
|
|
1006
972
|
self.ip_version = address.version
|
|
1007
973
|
|
|
1008
|
-
def
|
|
1009
|
-
return
|
|
974
|
+
def get_duplicates(self):
|
|
975
|
+
return IPAddress.objects.filter(vrf=self.vrf, host=self.host).exclude(pk=self.pk)
|
|
976
|
+
|
|
977
|
+
# TODO: The current IPAddress model has no appropriate natural key available yet.
|
|
978
|
+
natural_key_field_names = ["id"]
|
|
1010
979
|
|
|
1011
980
|
@classproperty # https://github.com/PyCQA/pylint-django/issues/240
|
|
1012
981
|
def STATUS_SLAAC(cls): # pylint: disable=no-self-argument
|
|
@@ -1022,26 +991,11 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1022
991
|
def clean(self):
|
|
1023
992
|
super().clean()
|
|
1024
993
|
|
|
1025
|
-
#
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
# Check for primary IP assignment that doesn't match the assigned device/VM if and only if
|
|
1031
|
-
# "_primary_ip_unset" has not been set by the caller.
|
|
1032
|
-
if self.present_in_database and not primary_ip_unset_by_form:
|
|
1033
|
-
device = Device.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
|
|
1034
|
-
if device:
|
|
1035
|
-
if getattr(self.assigned_object, "device", None) != device:
|
|
1036
|
-
raise ValidationError(
|
|
1037
|
-
{"interface": f"IP address is primary for device {device} but not assigned to it!"}
|
|
1038
|
-
)
|
|
1039
|
-
vm = VirtualMachine.objects.filter(Q(primary_ip4=self) | Q(primary_ip6=self)).first()
|
|
1040
|
-
if vm:
|
|
1041
|
-
if getattr(self.assigned_object, "virtual_machine", None) != vm:
|
|
1042
|
-
raise ValidationError(
|
|
1043
|
-
{"vminterface": f"IP address is primary for virtual machine {vm} but not assigned to it!"}
|
|
1044
|
-
)
|
|
994
|
+
# Validate that host is not being modified
|
|
995
|
+
if self.present_in_database:
|
|
996
|
+
ip_address = IPAddress.objects.get(id=self.id)
|
|
997
|
+
if ip_address.host != self.host:
|
|
998
|
+
raise ValidationError({"address": "Host address cannot be changed once created"})
|
|
1045
999
|
|
|
1046
1000
|
# Validate IP status selection
|
|
1047
1001
|
if self.status == IPAddress.STATUS_SLAAC and self.ip_version != 6:
|
|
@@ -1063,28 +1017,10 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1063
1017
|
|
|
1064
1018
|
super().save(*args, **kwargs)
|
|
1065
1019
|
|
|
1066
|
-
def to_csv(self):
|
|
1067
|
-
# Determine if this IP is primary for a Device
|
|
1068
|
-
is_primary = False
|
|
1069
|
-
if self.address.version == 4 and getattr(self, "primary_ip4_for", False):
|
|
1070
|
-
is_primary = True
|
|
1071
|
-
elif self.address.version == 6 and getattr(self, "primary_ip6_for", False):
|
|
1072
|
-
is_primary = True
|
|
1073
|
-
|
|
1074
|
-
return (
|
|
1075
|
-
self.address,
|
|
1076
|
-
self.tenant.name if self.tenant else None,
|
|
1077
|
-
self.get_status_display(),
|
|
1078
|
-
self.role.name if self.role else None,
|
|
1079
|
-
str(is_primary),
|
|
1080
|
-
self.dns_name,
|
|
1081
|
-
self.description,
|
|
1082
|
-
)
|
|
1083
|
-
|
|
1084
1020
|
@property
|
|
1085
1021
|
def address(self):
|
|
1086
|
-
if self.host is not None and self.
|
|
1087
|
-
cidr = f"{self.host}/{self.
|
|
1022
|
+
if self.host is not None and self.mask_length is not None:
|
|
1023
|
+
cidr = f"{self.host}/{self.mask_length}"
|
|
1088
1024
|
return netaddr.IPNetwork(cidr)
|
|
1089
1025
|
return None
|
|
1090
1026
|
|
|
@@ -1137,16 +1073,6 @@ class IPAddress(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1137
1073
|
def __str__(self):
|
|
1138
1074
|
return f"Multiple IPAddress objects specify this object (pk: {self.obj.pk}) as nat_inside. Please refer to nat_outside_list."
|
|
1139
1075
|
|
|
1140
|
-
def _set_mask_length(self, value):
|
|
1141
|
-
"""
|
|
1142
|
-
Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
|
|
1143
|
-
e.g. for bulk editing.
|
|
1144
|
-
"""
|
|
1145
|
-
if self.address is not None:
|
|
1146
|
-
self.prefix_length = value
|
|
1147
|
-
|
|
1148
|
-
mask_length = property(fset=_set_mask_length)
|
|
1149
|
-
|
|
1150
1076
|
|
|
1151
1077
|
class IPAddressToInterface(BaseModel):
|
|
1152
1078
|
ip_address = models.ForeignKey("ipam.IPAddress", on_delete=models.CASCADE, related_name="+")
|
|
@@ -1168,19 +1094,11 @@ class IPAddressToInterface(BaseModel):
|
|
|
1168
1094
|
is_secondary = models.BooleanField(default=False, help_text="Is secondary address on interface")
|
|
1169
1095
|
is_standby = models.BooleanField(default=False, help_text="Is standby address on interface")
|
|
1170
1096
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
if IPAddressToInterface.objects.filter(
|
|
1177
|
-
ip_address=self.ip_address,
|
|
1178
|
-
interface=self.interface,
|
|
1179
|
-
vm_interface=self.vm_interface,
|
|
1180
|
-
).exists():
|
|
1181
|
-
raise ValidationError(
|
|
1182
|
-
"IPAddressToInterface with this ip_address, interface and vm_interface already exists."
|
|
1183
|
-
)
|
|
1097
|
+
class Meta:
|
|
1098
|
+
unique_together = [
|
|
1099
|
+
["ip_address", "interface"],
|
|
1100
|
+
["ip_address", "vm_interface"],
|
|
1101
|
+
]
|
|
1184
1102
|
|
|
1185
1103
|
def clean(self):
|
|
1186
1104
|
super().clean()
|
|
@@ -1222,8 +1140,6 @@ class VLANGroup(OrganizationalModel):
|
|
|
1222
1140
|
)
|
|
1223
1141
|
description = models.CharField(max_length=200, blank=True)
|
|
1224
1142
|
|
|
1225
|
-
csv_headers = ["name", "slug", "location", "description"]
|
|
1226
|
-
|
|
1227
1143
|
class Meta:
|
|
1228
1144
|
ordering = (
|
|
1229
1145
|
"location",
|
|
@@ -1254,17 +1170,6 @@ class VLANGroup(OrganizationalModel):
|
|
|
1254
1170
|
def __str__(self):
|
|
1255
1171
|
return self.name
|
|
1256
1172
|
|
|
1257
|
-
def get_absolute_url(self):
|
|
1258
|
-
return reverse("ipam:vlangroup", args=[self.pk])
|
|
1259
|
-
|
|
1260
|
-
def to_csv(self):
|
|
1261
|
-
return (
|
|
1262
|
-
self.name,
|
|
1263
|
-
self.slug,
|
|
1264
|
-
self.location.name if self.location else None,
|
|
1265
|
-
self.description,
|
|
1266
|
-
)
|
|
1267
|
-
|
|
1268
1173
|
def get_next_available_vid(self):
|
|
1269
1174
|
"""
|
|
1270
1175
|
Return the first available VLAN ID (1-4094) in the group.
|
|
@@ -1285,7 +1190,7 @@ class VLANGroup(OrganizationalModel):
|
|
|
1285
1190
|
"statuses",
|
|
1286
1191
|
"webhooks",
|
|
1287
1192
|
)
|
|
1288
|
-
class VLAN(PrimaryModel
|
|
1193
|
+
class VLAN(PrimaryModel):
|
|
1289
1194
|
"""
|
|
1290
1195
|
A VLAN is a distinct layer two forwarding domain identified by a 12-bit integer (1-4094).
|
|
1291
1196
|
Each VLAN must be assigned to a Location, however VLAN IDs need not be unique within a Location.
|
|
@@ -1313,6 +1218,8 @@ class VLAN(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1313
1218
|
verbose_name="ID", validators=[MinValueValidator(1), MaxValueValidator(4094)]
|
|
1314
1219
|
)
|
|
1315
1220
|
name = models.CharField(max_length=255, db_index=True)
|
|
1221
|
+
status = StatusField(blank=False, null=False)
|
|
1222
|
+
role = RoleField(blank=True, null=True)
|
|
1316
1223
|
tenant = models.ForeignKey(
|
|
1317
1224
|
to="tenancy.Tenant",
|
|
1318
1225
|
on_delete=models.PROTECT,
|
|
@@ -1322,16 +1229,6 @@ class VLAN(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1322
1229
|
)
|
|
1323
1230
|
description = models.CharField(max_length=200, blank=True)
|
|
1324
1231
|
|
|
1325
|
-
csv_headers = [
|
|
1326
|
-
"location",
|
|
1327
|
-
"vlan_group",
|
|
1328
|
-
"vid",
|
|
1329
|
-
"name",
|
|
1330
|
-
"tenant",
|
|
1331
|
-
"status",
|
|
1332
|
-
"role",
|
|
1333
|
-
"description",
|
|
1334
|
-
]
|
|
1335
1232
|
clone_fields = [
|
|
1336
1233
|
"location",
|
|
1337
1234
|
"vlan_group",
|
|
@@ -1341,6 +1238,8 @@ class VLAN(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1341
1238
|
"description",
|
|
1342
1239
|
]
|
|
1343
1240
|
|
|
1241
|
+
natural_key_field_names = ["vid", "vlan_group"]
|
|
1242
|
+
|
|
1344
1243
|
class Meta:
|
|
1345
1244
|
ordering = (
|
|
1346
1245
|
"location",
|
|
@@ -1359,9 +1258,6 @@ class VLAN(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1359
1258
|
def __str__(self):
|
|
1360
1259
|
return self.display or super().__str__()
|
|
1361
1260
|
|
|
1362
|
-
def get_absolute_url(self):
|
|
1363
|
-
return reverse("ipam:vlan", args=[self.pk])
|
|
1364
|
-
|
|
1365
1261
|
def clean(self):
|
|
1366
1262
|
super().clean()
|
|
1367
1263
|
|
|
@@ -1385,18 +1281,6 @@ class VLAN(PrimaryModel, StatusModel, RoleModelMixin):
|
|
|
1385
1281
|
}
|
|
1386
1282
|
)
|
|
1387
1283
|
|
|
1388
|
-
def to_csv(self):
|
|
1389
|
-
return (
|
|
1390
|
-
self.location.name if self.location else None,
|
|
1391
|
-
self.vlan_group.name if self.vlan_group else None,
|
|
1392
|
-
self.vid,
|
|
1393
|
-
self.name,
|
|
1394
|
-
self.tenant.name if self.tenant else None,
|
|
1395
|
-
self.get_status_display(),
|
|
1396
|
-
self.role.name if self.role else None,
|
|
1397
|
-
self.description,
|
|
1398
|
-
)
|
|
1399
|
-
|
|
1400
1284
|
@property
|
|
1401
1285
|
def display(self):
|
|
1402
1286
|
return f"{self.name} ({self.vid})"
|
|
@@ -1457,15 +1341,6 @@ class Service(PrimaryModel):
|
|
|
1457
1341
|
)
|
|
1458
1342
|
description = models.CharField(max_length=200, blank=True)
|
|
1459
1343
|
|
|
1460
|
-
csv_headers = [
|
|
1461
|
-
"device",
|
|
1462
|
-
"virtual_machine",
|
|
1463
|
-
"name",
|
|
1464
|
-
"protocol",
|
|
1465
|
-
"ports",
|
|
1466
|
-
"description",
|
|
1467
|
-
]
|
|
1468
|
-
|
|
1469
1344
|
class Meta:
|
|
1470
1345
|
ordering = (
|
|
1471
1346
|
"protocol",
|
|
@@ -1475,13 +1350,12 @@ class Service(PrimaryModel):
|
|
|
1475
1350
|
def __str__(self):
|
|
1476
1351
|
return f"{self.name} ({self.get_protocol_display()}/{self.port_list})"
|
|
1477
1352
|
|
|
1478
|
-
def get_absolute_url(self):
|
|
1479
|
-
return reverse("ipam:service", args=[self.pk])
|
|
1480
|
-
|
|
1481
1353
|
@property
|
|
1482
1354
|
def parent(self):
|
|
1483
1355
|
return self.device or self.virtual_machine
|
|
1484
1356
|
|
|
1357
|
+
natural_key_field_names = ["name", "device", "virtual_machine"]
|
|
1358
|
+
|
|
1485
1359
|
def clean(self):
|
|
1486
1360
|
super().clean()
|
|
1487
1361
|
|
|
@@ -1491,16 +1365,6 @@ class Service(PrimaryModel):
|
|
|
1491
1365
|
if not self.device and not self.virtual_machine:
|
|
1492
1366
|
raise ValidationError("A service must be associated with either a device or a virtual machine.")
|
|
1493
1367
|
|
|
1494
|
-
def to_csv(self):
|
|
1495
|
-
return (
|
|
1496
|
-
self.device.name if self.device else None,
|
|
1497
|
-
self.virtual_machine.name if self.virtual_machine else None,
|
|
1498
|
-
self.name,
|
|
1499
|
-
self.get_protocol_display(),
|
|
1500
|
-
self.ports,
|
|
1501
|
-
self.description,
|
|
1502
|
-
)
|
|
1503
|
-
|
|
1504
1368
|
@property
|
|
1505
1369
|
def port_list(self):
|
|
1506
1370
|
return array_to_string(self.ports)
|