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.
Files changed (780) hide show
  1. nautobot/apps/api.py +6 -8
  2. nautobot/apps/forms.py +0 -2
  3. nautobot/apps/ui.py +0 -8
  4. nautobot/circuits/api/serializers.py +9 -117
  5. nautobot/circuits/api/urls.py +1 -1
  6. nautobot/circuits/api/views.py +0 -1
  7. nautobot/circuits/forms.py +0 -65
  8. nautobot/circuits/migrations/0014_related_name_changes.py +1 -1
  9. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  10. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  11. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  12. nautobot/circuits/models.py +3 -87
  13. nautobot/circuits/navigation.py +14 -69
  14. nautobot/circuits/signals.py +0 -2
  15. nautobot/circuits/tables.py +39 -1
  16. nautobot/circuits/tests/integration/test_relationships.py +9 -9
  17. nautobot/circuits/tests/test_api.py +4 -8
  18. nautobot/circuits/tests/test_filters.py +10 -4
  19. nautobot/circuits/tests/test_models.py +5 -1
  20. nautobot/circuits/tests/test_views.py +27 -5
  21. nautobot/circuits/views.py +18 -10
  22. nautobot/core/api/__init__.py +8 -2
  23. nautobot/core/api/fields.py +15 -6
  24. nautobot/core/api/filter_backends.py +3 -2
  25. nautobot/core/api/metadata.py +237 -30
  26. nautobot/core/api/mixins.py +94 -0
  27. nautobot/core/api/pagination.py +4 -0
  28. nautobot/core/api/parsers.py +154 -0
  29. nautobot/core/api/renderers.py +153 -2
  30. nautobot/core/api/schema.py +46 -2
  31. nautobot/core/api/serializers.py +377 -35
  32. nautobot/core/api/urls.py +11 -3
  33. nautobot/core/api/utils.py +174 -2
  34. nautobot/core/api/versioning.py +32 -10
  35. nautobot/core/api/views.py +266 -72
  36. nautobot/core/apps/__init__.py +138 -220
  37. nautobot/core/celery/__init__.py +112 -41
  38. nautobot/core/celery/backends.py +19 -12
  39. nautobot/core/celery/control.py +46 -0
  40. nautobot/core/celery/encoders.py +53 -0
  41. nautobot/core/celery/log.py +38 -0
  42. nautobot/core/celery/schedulers.py +23 -4
  43. nautobot/core/celery/task.py +1 -16
  44. nautobot/core/checks.py +0 -27
  45. nautobot/core/choices.py +0 -113
  46. nautobot/core/{cli.py → cli/__init__.py} +1 -1
  47. nautobot/core/cli/__main__.py +3 -0
  48. nautobot/core/constants.py +0 -24
  49. nautobot/core/context_processors.py +12 -0
  50. nautobot/core/filters.py +2 -2
  51. nautobot/core/forms/__init__.py +0 -4
  52. nautobot/core/forms/fields.py +38 -65
  53. nautobot/core/forms/forms.py +4 -1
  54. nautobot/core/forms/utils.py +0 -52
  55. nautobot/core/graphql/schema.py +4 -27
  56. nautobot/core/jobs/__init__.py +75 -0
  57. nautobot/core/management/commands/build_ui.py +255 -0
  58. nautobot/core/management/commands/generate_test_data.py +3 -2
  59. nautobot/core/management/commands/post_upgrade.py +24 -24
  60. nautobot/core/models/__init__.py +26 -1
  61. nautobot/core/models/fields.py +24 -5
  62. nautobot/core/models/generics.py +2 -42
  63. nautobot/core/models/managers.py +5 -0
  64. nautobot/core/models/name_color_content_types.py +0 -14
  65. nautobot/core/models/tree_queries.py +14 -4
  66. nautobot/core/models/utils.py +5 -6
  67. nautobot/core/models/validators.py +17 -8
  68. nautobot/core/releases.py +8 -10
  69. nautobot/core/settings.py +80 -42
  70. nautobot/core/tables.py +5 -5
  71. nautobot/core/tasks.py +4 -7
  72. nautobot/core/templates/base.html +1 -49
  73. nautobot/core/templates/base_django.html +49 -0
  74. nautobot/core/templates/base_react.html +55 -0
  75. nautobot/core/templates/buttons/export.html +6 -4
  76. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  77. nautobot/core/templates/generic/object_list.html +3 -1
  78. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  79. nautobot/core/templates/inc/footer.html +1 -0
  80. nautobot/core/templates/inc/javascript.html +0 -14
  81. nautobot/core/templates/inc/nav_menu.html +28 -33
  82. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  83. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  84. nautobot/core/templates/nautobot_config.py.j2 +8 -20
  85. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  86. nautobot/core/templates/rest_framework/api.html +8 -0
  87. nautobot/core/templatetags/buttons.py +32 -28
  88. nautobot/core/testing/__init__.py +47 -44
  89. nautobot/core/testing/api.py +362 -47
  90. nautobot/core/testing/filters.py +1 -1
  91. nautobot/core/testing/migrations.py +2 -0
  92. nautobot/core/testing/mixins.py +22 -9
  93. nautobot/core/testing/schema.py +2 -1
  94. nautobot/core/testing/views.py +21 -46
  95. nautobot/core/tests/integration/test_filters.py +17 -8
  96. nautobot/core/tests/integration/test_navbar.py +11 -34
  97. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  98. nautobot/core/tests/nautobot_config.py +2 -3
  99. nautobot/core/tests/test_api.py +290 -21
  100. nautobot/core/tests/test_checks.py +0 -7
  101. nautobot/core/tests/test_filters.py +107 -59
  102. nautobot/core/tests/test_forms.py +26 -92
  103. nautobot/core/tests/test_graphql.py +110 -77
  104. nautobot/core/tests/test_logging.py +4 -0
  105. nautobot/core/tests/test_managers.py +3 -1
  106. nautobot/core/tests/test_models.py +2 -0
  107. nautobot/core/tests/test_paginator.py +3 -1
  108. nautobot/core/tests/test_releases.py +12 -12
  109. nautobot/core/tests/test_templatetags_helpers.py +4 -4
  110. nautobot/core/tests/test_utils.py +32 -68
  111. nautobot/core/tests/test_views.py +12 -15
  112. nautobot/core/utils/data.py +17 -0
  113. nautobot/core/utils/deprecation.py +9 -6
  114. nautobot/core/utils/filtering.py +8 -3
  115. nautobot/core/utils/git.py +12 -4
  116. nautobot/core/utils/lookup.py +3 -1
  117. nautobot/core/utils/requests.py +1 -104
  118. nautobot/core/views/__init__.py +1 -0
  119. nautobot/core/views/generic.py +75 -110
  120. nautobot/core/views/mixins.py +52 -61
  121. nautobot/core/views/renderers.py +6 -7
  122. nautobot/core/views/utils.py +80 -0
  123. nautobot/dcim/api/serializers.py +160 -667
  124. nautobot/dcim/api/urls.py +1 -1
  125. nautobot/dcim/api/views.py +7 -44
  126. nautobot/dcim/choices.py +2 -0
  127. nautobot/dcim/filters/__init__.py +21 -0
  128. nautobot/dcim/form_mixins.py +1 -27
  129. nautobot/dcim/forms.py +19 -765
  130. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -1
  131. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -13
  132. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -1
  133. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -1
  134. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  135. nautobot/dcim/migrations/0035_related_name_changes.py +1 -1
  136. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -1
  137. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  138. nautobot/dcim/migrations/{0040_ipam__namespaces.py → 0041_ipam__namespaces.py} +1 -1
  139. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  140. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  141. nautobot/dcim/models/cables.py +3 -33
  142. nautobot/dcim/models/device_component_templates.py +6 -0
  143. nautobot/dcim/models/device_components.py +12 -198
  144. nautobot/dcim/models/devices.py +30 -143
  145. nautobot/dcim/models/locations.py +3 -64
  146. nautobot/dcim/models/power.py +3 -50
  147. nautobot/dcim/models/racks.py +7 -84
  148. nautobot/dcim/navigation.py +141 -467
  149. nautobot/dcim/signals.py +0 -2
  150. nautobot/dcim/tables/locations.py +2 -2
  151. nautobot/dcim/tables/power.py +1 -2
  152. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  153. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  154. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  155. nautobot/dcim/templates/dcim/location.html +16 -1
  156. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  157. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  158. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  159. nautobot/dcim/tests/test_api.py +166 -81
  160. nautobot/dcim/tests/test_cablepaths.py +41 -35
  161. nautobot/dcim/tests/test_filters.py +67 -23
  162. nautobot/dcim/tests/test_forms.py +5 -205
  163. nautobot/dcim/tests/test_graphql.py +7 -2
  164. nautobot/dcim/tests/test_migrations.py +6 -11
  165. nautobot/dcim/tests/test_models.py +182 -110
  166. nautobot/dcim/tests/test_natural_ordering.py +11 -8
  167. nautobot/dcim/tests/test_signals.py +6 -3
  168. nautobot/dcim/tests/test_views.py +197 -175
  169. nautobot/dcim/urls.py +11 -16
  170. nautobot/dcim/views.py +7 -134
  171. nautobot/docs/additional-features/caching.md +6 -87
  172. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  173. nautobot/docs/additional-features/jobs.md +177 -195
  174. nautobot/docs/administration/nautobot-server.md +6 -21
  175. nautobot/docs/administration/replicating-nautobot.md +0 -10
  176. nautobot/docs/configuration/optional-settings.md +32 -41
  177. nautobot/docs/configuration/required-settings.md +11 -52
  178. nautobot/docs/development/application-registry.md +2 -13
  179. nautobot/docs/development/extending-models.md +15 -17
  180. nautobot/docs/development/generic-views.md +0 -2
  181. nautobot/docs/development/getting-started.md +55 -5
  182. nautobot/docs/development/navigation-menu.md +22 -93
  183. nautobot/docs/development/react-ui.md +105 -0
  184. nautobot/docs/development/role-internals.md +1 -3
  185. nautobot/docs/development/style-guide.md +6 -4
  186. nautobot/docs/index.md +3 -2
  187. nautobot/docs/installation/migrating-from-netbox.md +11 -42
  188. nautobot/docs/installation/nautobot.md +1 -1
  189. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  190. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  191. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  192. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  193. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  194. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  195. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  196. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  197. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  198. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  199. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  200. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  201. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  202. nautobot/docs/installation/upgrading-from-nautobot-v1.md +170 -747
  203. nautobot/docs/models/dcim/device.md +3 -0
  204. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  205. nautobot/docs/models/extras/computedfield.md +4 -4
  206. nautobot/docs/models/extras/gitrepository.md +3 -0
  207. nautobot/docs/models/extras/job.md +1 -0
  208. nautobot/docs/models/extras/jobbutton.md +18 -13
  209. nautobot/docs/models/extras/jobhook.md +7 -4
  210. nautobot/docs/models/extras/jobresult.md +6 -2
  211. nautobot/docs/models/extras/relationship.md +2 -2
  212. nautobot/docs/models/extras/status.md +6 -19
  213. nautobot/docs/models/ipam/ipaddress.md +3 -0
  214. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  215. nautobot/docs/plugins/development.md +83 -21
  216. nautobot/docs/release-notes/version-1.5.md +53 -0
  217. nautobot/docs/release-notes/version-2.0.md +180 -0
  218. nautobot/docs/requirements.txt +1 -0
  219. nautobot/docs/rest-api/overview.md +384 -215
  220. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  221. nautobot/extras/admin.py +3 -5
  222. nautobot/extras/api/customfields.py +15 -39
  223. nautobot/extras/api/fields.py +0 -11
  224. nautobot/extras/api/mixins.py +45 -0
  225. nautobot/extras/api/relationships.py +63 -158
  226. nautobot/extras/api/serializers.py +165 -700
  227. nautobot/extras/api/urls.py +1 -1
  228. nautobot/extras/api/views.py +294 -280
  229. nautobot/extras/apps.py +4 -7
  230. nautobot/extras/choices.py +11 -9
  231. nautobot/extras/constants.py +9 -3
  232. nautobot/extras/datasources/__init__.py +2 -0
  233. nautobot/extras/datasources/git.py +135 -186
  234. nautobot/extras/datasources/registry.py +25 -35
  235. nautobot/extras/filters/__init__.py +20 -19
  236. nautobot/extras/filters/mixins.py +4 -4
  237. nautobot/extras/forms/forms.py +63 -127
  238. nautobot/extras/forms/mixins.py +23 -51
  239. nautobot/extras/health_checks.py +0 -33
  240. nautobot/extras/jobs.py +387 -565
  241. nautobot/extras/management/commands/runjob.py +24 -62
  242. nautobot/extras/managers.py +30 -7
  243. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  244. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -1
  245. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -1
  246. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -7
  247. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -32
  248. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -1
  249. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -1
  250. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  251. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -1
  252. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -2
  253. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -1
  254. 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
  255. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  256. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -1
  257. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  258. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -9
  259. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -1
  260. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  261. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  262. 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
  263. nautobot/extras/migrations/{0077_remove_slug.py → 0078_remove_slug.py} +1 -5
  264. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  265. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  266. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  267. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  268. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  269. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  270. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  271. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  272. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  273. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  274. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  275. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  276. nautobot/extras/models/__init__.py +2 -3
  277. nautobot/extras/models/change_logging.py +0 -36
  278. nautobot/extras/models/customfields.py +39 -33
  279. nautobot/extras/models/datasources.py +48 -50
  280. nautobot/extras/models/groups.py +5 -6
  281. nautobot/extras/models/jobs.py +189 -321
  282. nautobot/extras/models/mixins.py +0 -71
  283. nautobot/extras/models/models.py +0 -19
  284. nautobot/extras/models/relationships.py +19 -13
  285. nautobot/extras/models/roles.py +0 -34
  286. nautobot/extras/models/secrets.py +2 -26
  287. nautobot/extras/models/statuses.py +6 -5
  288. nautobot/extras/models/tags.py +2 -17
  289. nautobot/extras/navigation.py +89 -307
  290. nautobot/extras/plugins/__init__.py +3 -120
  291. nautobot/extras/plugins/utils.py +0 -3
  292. nautobot/extras/plugins/validators.py +5 -4
  293. nautobot/extras/plugins/views.py +16 -3
  294. nautobot/extras/querysets.py +1 -7
  295. nautobot/extras/registry.py +3 -0
  296. nautobot/extras/signals.py +26 -60
  297. nautobot/extras/tables.py +34 -40
  298. nautobot/extras/tasks.py +0 -12
  299. nautobot/extras/templates/extras/configcontext.html +1 -1
  300. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  301. nautobot/extras/templates/extras/customfield.html +0 -13
  302. nautobot/extras/templates/extras/gitrepository.html +3 -3
  303. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  304. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  305. nautobot/extras/templates/extras/job.html +35 -25
  306. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  307. nautobot/extras/templates/extras/job_detail.html +13 -31
  308. nautobot/extras/templates/extras/job_edit.html +15 -17
  309. nautobot/extras/templates/extras/jobresult.html +24 -6
  310. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  311. nautobot/extras/templates/extras/secret.html +28 -0
  312. nautobot/extras/templatetags/job_buttons.py +1 -0
  313. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  314. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  315. nautobot/extras/test_jobs/dry_run.py +29 -0
  316. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  317. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  318. nautobot/extras/test_jobs/fail.py +23 -0
  319. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  320. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  321. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  322. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  323. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  324. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  325. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  326. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  327. nautobot/extras/test_jobs/job_variables.py +88 -0
  328. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  329. nautobot/extras/test_jobs/log_redaction.py +20 -0
  330. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  331. nautobot/extras/test_jobs/modify_db.py +25 -0
  332. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  333. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  334. nautobot/extras/test_jobs/object_var_required.py +21 -0
  335. nautobot/extras/test_jobs/object_vars.py +26 -0
  336. nautobot/extras/test_jobs/pass.py +25 -0
  337. nautobot/extras/test_jobs/profiling.py +32 -0
  338. nautobot/extras/test_jobs/read_only_job.py +15 -0
  339. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  340. 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
  341. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  342. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  343. nautobot/extras/tests/integration/test_configcontextschema.py +5 -3
  344. nautobot/extras/tests/integration/test_customfields.py +4 -2
  345. nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
  346. nautobot/extras/tests/integration/test_jobs.py +25 -27
  347. nautobot/extras/tests/integration/test_notes.py +8 -4
  348. nautobot/extras/tests/integration/test_relationships.py +2 -2
  349. nautobot/extras/tests/test_api.py +649 -642
  350. nautobot/extras/tests/test_changelog.py +3 -3
  351. nautobot/extras/tests/test_context_managers.py +5 -3
  352. nautobot/extras/tests/test_customfields.py +92 -50
  353. nautobot/extras/tests/test_datasources.py +189 -112
  354. nautobot/extras/tests/test_dynamicgroups.py +7 -8
  355. nautobot/extras/tests/test_filters.py +137 -89
  356. nautobot/extras/tests/test_forms.py +73 -75
  357. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  358. nautobot/extras/tests/test_jobs.py +262 -263
  359. nautobot/extras/tests/test_migrations.py +4 -3
  360. nautobot/extras/tests/test_models.py +116 -161
  361. nautobot/extras/tests/test_plugins.py +38 -60
  362. nautobot/extras/tests/test_relationships.py +167 -120
  363. nautobot/extras/tests/test_tags.py +6 -11
  364. nautobot/extras/tests/test_utils.py +31 -1
  365. nautobot/extras/tests/test_views.py +201 -145
  366. nautobot/extras/tests/test_webhooks.py +6 -2
  367. nautobot/extras/urls.py +42 -42
  368. nautobot/extras/utils.py +137 -163
  369. nautobot/extras/views.py +78 -152
  370. nautobot/ipam/api/fields.py +17 -0
  371. nautobot/ipam/api/serializers.py +58 -164
  372. nautobot/ipam/api/urls.py +1 -1
  373. nautobot/ipam/api/views.py +3 -2
  374. nautobot/ipam/apps.py +1 -2
  375. nautobot/ipam/filters.py +1 -10
  376. nautobot/ipam/forms.py +4 -177
  377. nautobot/ipam/lookups.py +1 -0
  378. nautobot/ipam/management/commands/__init__.py +0 -0
  379. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  380. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -1
  381. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -38
  382. nautobot/ipam/migrations/0020_related_name_changes.py +1 -1
  383. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +2 -2
  384. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  385. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  386. nautobot/ipam/migrations/{0028_ipam__namespaces.py → 0030_ipam__namespaces.py} +77 -28
  387. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  388. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  389. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  390. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  391. nautobot/ipam/models.py +100 -236
  392. nautobot/ipam/navigation.py +36 -181
  393. nautobot/ipam/querysets.py +20 -25
  394. nautobot/ipam/signals.py +49 -6
  395. nautobot/ipam/tables.py +10 -3
  396. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  397. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  398. nautobot/ipam/templates/ipam/namespace_retrieve.html +17 -4
  399. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  400. nautobot/ipam/templates/ipam/prefix.html +1 -1
  401. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  402. nautobot/ipam/templates/ipam/vrf_edit.html +6 -0
  403. nautobot/ipam/tests/integration/test_prefixes.py +3 -26
  404. nautobot/ipam/tests/test_api.py +22 -19
  405. nautobot/ipam/tests/test_filters.py +59 -23
  406. nautobot/ipam/tests/test_migrations.py +6 -10
  407. nautobot/ipam/tests/test_models.py +323 -198
  408. nautobot/ipam/tests/test_ordering.py +2 -2
  409. nautobot/ipam/tests/test_querysets.py +44 -24
  410. nautobot/ipam/tests/test_views.py +73 -26
  411. nautobot/ipam/urls.py +16 -0
  412. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  413. nautobot/ipam/utils/migrations.py +713 -0
  414. nautobot/ipam/views.py +137 -20
  415. nautobot/project-static/docs/404.html +1178 -10
  416. nautobot/project-static/docs/additional-features/caching.html +1224 -159
  417. nautobot/project-static/docs/additional-features/change-logging.html +1180 -12
  418. nautobot/project-static/docs/additional-features/config-contexts.html +1180 -12
  419. nautobot/project-static/docs/additional-features/graphql.html +1179 -11
  420. nautobot/project-static/docs/additional-features/healthcheck.html +1180 -12
  421. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1184 -12
  422. nautobot/project-static/docs/additional-features/jobs.html +1514 -328
  423. nautobot/project-static/docs/additional-features/napalm.html +1180 -12
  424. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1180 -12
  425. nautobot/project-static/docs/additional-features/template-filters.html +1180 -12
  426. nautobot/project-static/docs/administration/celery-queues.html +1178 -10
  427. nautobot/project-static/docs/administration/nautobot-server.html +1451 -304
  428. nautobot/project-static/docs/administration/nautobot-shell.html +1178 -10
  429. nautobot/project-static/docs/administration/permissions.html +1178 -10
  430. nautobot/project-static/docs/administration/replicating-nautobot.html +1262 -113
  431. nautobot/project-static/docs/apps/index.html +1178 -10
  432. nautobot/project-static/docs/apps/nautobot-apps.html +1178 -10
  433. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1580 -426
  434. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1178 -10
  435. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3481 -1838
  436. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1178 -10
  437. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1178 -10
  438. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1185 -11
  439. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1719 -551
  440. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2062 -930
  441. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +1946 -659
  442. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1180 -12
  443. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1189 -21
  444. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +9283 -6218
  445. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2734 -2122
  446. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1178 -10
  447. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2337 -1300
  448. nautobot/project-static/docs/configuration/authentication/ldap.html +1178 -10
  449. nautobot/project-static/docs/configuration/authentication/remote.html +1178 -10
  450. nautobot/project-static/docs/configuration/authentication/sso.html +1178 -10
  451. nautobot/project-static/docs/configuration/index.html +1178 -10
  452. nautobot/project-static/docs/configuration/optional-settings.html +1311 -160
  453. nautobot/project-static/docs/configuration/required-settings.html +1312 -211
  454. nautobot/project-static/docs/core-functionality/circuits.html +1178 -10
  455. nautobot/project-static/docs/core-functionality/device-types.html +1178 -10
  456. nautobot/project-static/docs/core-functionality/devices.html +1182 -10
  457. nautobot/project-static/docs/core-functionality/ipam.html +1182 -10
  458. nautobot/project-static/docs/core-functionality/power.html +1178 -10
  459. nautobot/project-static/docs/core-functionality/secrets.html +1178 -10
  460. nautobot/project-static/docs/core-functionality/services.html +1178 -10
  461. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1178 -10
  462. nautobot/project-static/docs/core-functionality/tenancy.html +1178 -10
  463. nautobot/project-static/docs/core-functionality/virtualization.html +1182 -10
  464. nautobot/project-static/docs/core-functionality/vlans.html +1179 -11
  465. nautobot/project-static/docs/development/application-registry.html +1190 -42
  466. nautobot/project-static/docs/development/best-practices.html +1178 -10
  467. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1178 -10
  468. nautobot/project-static/docs/development/extending-models.html +1238 -83
  469. nautobot/project-static/docs/development/generic-views.html +1180 -14
  470. nautobot/project-static/docs/development/getting-started.html +1365 -90
  471. nautobot/project-static/docs/development/homepage.html +1178 -10
  472. nautobot/project-static/docs/development/index.html +1178 -10
  473. nautobot/project-static/docs/development/model-features.html +1178 -10
  474. nautobot/project-static/docs/development/natural-keys.html +1178 -10
  475. nautobot/project-static/docs/development/navigation-menu.html +1215 -125
  476. nautobot/project-static/docs/development/react-ui.html +4199 -0
  477. nautobot/project-static/docs/development/release-checklist.html +1178 -10
  478. nautobot/project-static/docs/development/role-internals.html +1179 -12
  479. nautobot/project-static/docs/development/style-guide.html +1188 -19
  480. nautobot/project-static/docs/development/templates.html +1178 -10
  481. nautobot/project-static/docs/development/testing.html +1178 -10
  482. nautobot/project-static/docs/development/user-preferences.html +1178 -10
  483. nautobot/project-static/docs/docker/index.html +1178 -10
  484. nautobot/project-static/docs/index.html +1183 -12
  485. nautobot/project-static/docs/installation/centos.html +1178 -10
  486. nautobot/project-static/docs/installation/external-authentication.html +1178 -10
  487. nautobot/project-static/docs/installation/http-server.html +1178 -10
  488. nautobot/project-static/docs/installation/index.html +1178 -10
  489. nautobot/project-static/docs/installation/migrating-from-netbox.html +1305 -189
  490. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1178 -10
  491. nautobot/project-static/docs/installation/nautobot.html +1179 -11
  492. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1178 -10
  493. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1178 -10
  494. nautobot/project-static/docs/installation/services.html +1178 -10
  495. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  496. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  497. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  498. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  499. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  500. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  501. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  502. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  503. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  504. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  505. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  506. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  507. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  508. nautobot/project-static/docs/installation/ubuntu.html +1178 -10
  509. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +3823 -2152
  510. nautobot/project-static/docs/installation/upgrading.html +1178 -10
  511. nautobot/project-static/docs/models/circuits/circuit.html +1293 -103
  512. nautobot/project-static/docs/models/circuits/circuittermination.html +1293 -103
  513. nautobot/project-static/docs/models/circuits/circuittype.html +1293 -103
  514. nautobot/project-static/docs/models/circuits/provider.html +1293 -103
  515. nautobot/project-static/docs/models/circuits/providernetwork.html +1293 -103
  516. nautobot/project-static/docs/models/dcim/cable.html +1324 -103
  517. nautobot/project-static/docs/models/dcim/consoleport.html +1293 -103
  518. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1293 -103
  519. nautobot/project-static/docs/models/dcim/consoleserverport.html +1293 -103
  520. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1293 -103
  521. nautobot/project-static/docs/models/dcim/device.html +1326 -132
  522. nautobot/project-static/docs/models/dcim/devicebay.html +1293 -103
  523. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1293 -103
  524. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1379 -97
  525. nautobot/project-static/docs/models/dcim/devicetype.html +1293 -103
  526. nautobot/project-static/docs/models/dcim/frontport.html +1293 -103
  527. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1293 -103
  528. nautobot/project-static/docs/models/dcim/interface.html +1293 -103
  529. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1293 -103
  530. nautobot/project-static/docs/models/dcim/inventoryitem.html +1293 -103
  531. nautobot/project-static/docs/models/dcim/location.html +1293 -103
  532. nautobot/project-static/docs/models/dcim/locationtype.html +1293 -103
  533. nautobot/project-static/docs/models/dcim/manufacturer.html +1292 -102
  534. nautobot/project-static/docs/models/dcim/platform.html +1272 -82
  535. nautobot/project-static/docs/models/dcim/powerfeed.html +1270 -80
  536. nautobot/project-static/docs/models/dcim/poweroutlet.html +1272 -82
  537. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1272 -82
  538. nautobot/project-static/docs/models/dcim/powerpanel.html +1270 -80
  539. nautobot/project-static/docs/models/dcim/powerport.html +1272 -82
  540. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1272 -82
  541. nautobot/project-static/docs/models/dcim/rack.html +1272 -82
  542. nautobot/project-static/docs/models/dcim/rackgroup.html +1272 -82
  543. nautobot/project-static/docs/models/dcim/rackreservation.html +1272 -82
  544. nautobot/project-static/docs/models/dcim/rearport.html +1286 -96
  545. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1286 -96
  546. nautobot/project-static/docs/models/dcim/region.html +1178 -10
  547. nautobot/project-static/docs/models/dcim/site.html +1178 -10
  548. nautobot/project-static/docs/models/dcim/virtualchassis.html +1284 -94
  549. nautobot/project-static/docs/models/extras/computedfield.html +1184 -16
  550. nautobot/project-static/docs/models/extras/configcontext.html +1314 -86
  551. nautobot/project-static/docs/models/extras/configcontextschema.html +1276 -86
  552. nautobot/project-static/docs/models/extras/customfield.html +1180 -12
  553. nautobot/project-static/docs/models/extras/customlink.html +1180 -12
  554. nautobot/project-static/docs/models/extras/dynamicgroup.html +1180 -12
  555. nautobot/project-static/docs/models/extras/exporttemplate.html +1180 -12
  556. nautobot/project-static/docs/models/extras/gitrepository.html +1184 -12
  557. nautobot/project-static/docs/models/extras/graphqlquery.html +1321 -86
  558. nautobot/project-static/docs/models/extras/imageattachment.html +1276 -86
  559. nautobot/project-static/docs/models/extras/job.html +1277 -86
  560. nautobot/project-static/docs/models/extras/jobbutton.html +1201 -29
  561. nautobot/project-static/docs/models/extras/jobhook.html +1188 -16
  562. nautobot/project-static/docs/models/extras/joblogentry.html +1274 -84
  563. nautobot/project-static/docs/models/extras/jobresult.html +1364 -169
  564. nautobot/project-static/docs/models/extras/note.html +1180 -12
  565. nautobot/project-static/docs/models/extras/relationship.html +1182 -14
  566. nautobot/project-static/docs/models/extras/role.html +1320 -86
  567. nautobot/project-static/docs/models/extras/secret.html +1314 -86
  568. nautobot/project-static/docs/models/extras/secretsgroup.html +1276 -86
  569. nautobot/project-static/docs/models/extras/status.html +1188 -59
  570. nautobot/project-static/docs/models/extras/tag.html +1180 -12
  571. nautobot/project-static/docs/models/extras/webhook.html +1180 -12
  572. nautobot/project-static/docs/models/ipam/ipaddress.html +1327 -102
  573. nautobot/project-static/docs/models/ipam/prefix.html +1276 -86
  574. nautobot/project-static/docs/models/ipam/rir.html +1276 -86
  575. nautobot/project-static/docs/models/ipam/routetarget.html +1276 -86
  576. nautobot/project-static/docs/models/ipam/service.html +1276 -86
  577. nautobot/project-static/docs/models/ipam/vlan.html +1276 -86
  578. nautobot/project-static/docs/models/ipam/vlangroup.html +1276 -86
  579. nautobot/project-static/docs/models/ipam/vrf.html +1276 -86
  580. nautobot/project-static/docs/models/tenancy/tenant.html +1276 -86
  581. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1276 -86
  582. nautobot/project-static/docs/models/users/objectpermission.html +1314 -86
  583. nautobot/project-static/docs/models/users/token.html +1276 -86
  584. nautobot/project-static/docs/models/virtualization/cluster.html +1276 -86
  585. nautobot/project-static/docs/models/virtualization/clustergroup.html +1276 -86
  586. nautobot/project-static/docs/models/virtualization/clustertype.html +1276 -86
  587. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1321 -127
  588. nautobot/project-static/docs/models/virtualization/vminterface.html +1276 -86
  589. nautobot/project-static/docs/objects.inv +0 -0
  590. nautobot/project-static/docs/plugins/development.html +1726 -495
  591. nautobot/project-static/docs/plugins/index.html +1178 -10
  592. nautobot/project-static/docs/plugins/porting-from-netbox.html +1178 -10
  593. nautobot/project-static/docs/release-notes/index.html +1178 -10
  594. nautobot/project-static/docs/release-notes/version-1.0.html +1178 -10
  595. nautobot/project-static/docs/release-notes/version-1.1.html +1178 -10
  596. nautobot/project-static/docs/release-notes/version-1.2.html +1178 -10
  597. nautobot/project-static/docs/release-notes/version-1.3.html +1178 -10
  598. nautobot/project-static/docs/release-notes/version-1.4.html +1178 -10
  599. nautobot/project-static/docs/release-notes/version-1.5.html +1608 -225
  600. nautobot/project-static/docs/release-notes/version-2.0.html +1547 -47
  601. nautobot/project-static/docs/requirements.txt +1 -0
  602. nautobot/project-static/docs/rest-api/authentication.html +1179 -11
  603. nautobot/project-static/docs/rest-api/filtering.html +1178 -10
  604. nautobot/project-static/docs/rest-api/overview.html +1841 -446
  605. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  606. nautobot/project-static/docs/search/search_index.json +1 -1
  607. nautobot/project-static/docs/sitemap.xml +197 -187
  608. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  609. nautobot/project-static/docs/user-guides/custom-fields.html +1178 -10
  610. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1178 -10
  611. nautobot/project-static/docs/user-guides/getting-started/index.html +1178 -10
  612. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1178 -10
  613. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1178 -10
  614. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1178 -10
  615. nautobot/project-static/docs/user-guides/getting-started/regions.html +1178 -10
  616. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1178 -10
  617. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1178 -10
  618. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1178 -10
  619. nautobot/project-static/docs/user-guides/git-data-source.html +1178 -10
  620. nautobot/project-static/docs/user-guides/graphql.html +1178 -10
  621. nautobot/project-static/docs/user-guides/relationships.html +1178 -10
  622. nautobot/project-static/docs/user-guides/s3-django-storage.html +1178 -10
  623. nautobot/project-static/js/forms.js +16 -9
  624. nautobot/project-static/js/theme.js +5 -0
  625. nautobot/tenancy/api/serializers.py +4 -32
  626. nautobot/tenancy/api/urls.py +1 -1
  627. nautobot/tenancy/forms.py +0 -28
  628. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  629. nautobot/tenancy/models.py +0 -25
  630. nautobot/tenancy/navigation.py +6 -39
  631. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  632. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  633. nautobot/tenancy/tests/test_api.py +1 -3
  634. nautobot/tenancy/tests/test_filters.py +10 -5
  635. nautobot/tenancy/views.py +0 -2
  636. nautobot/ui/.eslintignore +6 -0
  637. nautobot/ui/.gitignore +10 -0
  638. nautobot/ui/.prettierignore +9 -0
  639. nautobot/ui/.prettierrc +4 -0
  640. nautobot/ui/README.md +33 -0
  641. nautobot/ui/app_imports.js.j2 +7 -0
  642. nautobot/ui/craco.config.js +46 -0
  643. nautobot/ui/jsconfig-base.json +11 -0
  644. nautobot/ui/jsconfig.json +5 -0
  645. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  646. nautobot/ui/package-lock.json +21451 -0
  647. nautobot/ui/package.json +70 -0
  648. nautobot/ui/public/index.html +47 -0
  649. nautobot/ui/public/logo192.png +0 -0
  650. nautobot/ui/public/logo512.png +0 -0
  651. nautobot/ui/public/manifest.json +25 -0
  652. nautobot/ui/public/nautobot_logo.svg +131 -0
  653. nautobot/ui/public/robots.txt +3 -0
  654. nautobot/ui/src/App.js +71 -0
  655. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  656. nautobot/ui/src/components/AppTab.js +40 -0
  657. nautobot/ui/src/components/Apps.js +60 -0
  658. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  659. nautobot/ui/src/components/HomePanel.js +58 -0
  660. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  661. nautobot/ui/src/components/Layout.js +53 -0
  662. nautobot/ui/src/components/LoadingWidget.js +25 -0
  663. nautobot/ui/src/components/Navbar.js +116 -0
  664. nautobot/ui/src/components/NotificationPopover.js +27 -0
  665. nautobot/ui/src/components/ObjectListTable.js +209 -0
  666. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  667. nautobot/ui/src/components/RouterButton.js +10 -0
  668. nautobot/ui/src/components/RouterLink.js +10 -0
  669. nautobot/ui/src/components/SidebarNav.js +147 -0
  670. nautobot/ui/src/components/Table.js +48 -0
  671. nautobot/ui/src/components/TableItem.js +71 -0
  672. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  673. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  674. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  675. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  676. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  677. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  678. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  679. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  680. nautobot/ui/src/components/pagination.js +93 -0
  681. nautobot/ui/src/components/paginator.js +79 -0
  682. nautobot/ui/src/components/paginator_form.js +43 -0
  683. nautobot/ui/src/components/usePagination.js +57 -0
  684. nautobot/ui/src/constants/apiPath.js +10 -0
  685. nautobot/ui/src/constants/icons.js +15 -0
  686. nautobot/ui/src/constants/size.js +15 -0
  687. nautobot/ui/src/index.js +65 -0
  688. nautobot/ui/src/reportWebVitals.js +15 -0
  689. nautobot/ui/src/router.js +77 -0
  690. nautobot/ui/src/utils/api.js +131 -0
  691. nautobot/ui/src/utils/app-import.js +15 -0
  692. nautobot/ui/src/utils/color.js +15 -0
  693. nautobot/ui/src/utils/date.js +14 -0
  694. nautobot/ui/src/utils/index.js +15 -0
  695. nautobot/ui/src/utils/navigation.js +32 -0
  696. nautobot/ui/src/utils/session.js +64 -0
  697. nautobot/ui/src/utils/store.js +242 -0
  698. nautobot/ui/src/utils/string.js +6 -0
  699. nautobot/ui/src/utils/url.js +4 -0
  700. nautobot/ui/src/views/Home.js +138 -0
  701. nautobot/ui/src/views/InstalledApps.js +80 -0
  702. nautobot/ui/src/views/Login.js +48 -0
  703. nautobot/ui/src/views/Logout.js +20 -0
  704. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  705. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  706. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  707. nautobot/ui/src/views/generic/GenericView.js +142 -0
  708. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  709. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  710. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  711. nautobot/users/admin.py +1 -1
  712. nautobot/users/api/serializers.py +51 -61
  713. nautobot/users/api/urls.py +1 -1
  714. nautobot/users/api/views.py +53 -2
  715. nautobot/users/tests/test_api.py +110 -25
  716. nautobot/virtualization/api/serializers.py +18 -130
  717. nautobot/virtualization/api/urls.py +1 -1
  718. nautobot/virtualization/api/views.py +1 -22
  719. nautobot/virtualization/forms.py +13 -99
  720. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -1
  721. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -11
  722. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -1
  723. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -1
  724. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  725. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  726. nautobot/virtualization/migrations/{0021_ipam__namespaces.py → 0023_ipam__namespaces.py} +2 -2
  727. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  728. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  729. nautobot/virtualization/models.py +31 -123
  730. nautobot/virtualization/navigation.py +18 -99
  731. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -1
  732. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  733. nautobot/virtualization/tests/test_api.py +25 -26
  734. nautobot/virtualization/tests/test_filters.py +41 -15
  735. nautobot/virtualization/tests/test_models.py +31 -7
  736. nautobot/virtualization/tests/test_views.py +42 -25
  737. nautobot/virtualization/views.py +7 -6
  738. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +3 -7
  739. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +744 -602
  740. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  741. nautobot/circuits/api/nested_serializers.py +0 -69
  742. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  743. nautobot/dcim/api/nested_serializers.py +0 -356
  744. nautobot/dcim/templates/dcim/device_import.html +0 -5
  745. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  746. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  747. nautobot/extras/api/nested_serializers.py +0 -353
  748. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -41
  749. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -46
  750. nautobot/extras/reports.py +0 -60
  751. nautobot/extras/scripts.py +0 -72
  752. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  753. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  754. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  755. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  756. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  757. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  758. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  759. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  760. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  761. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -18
  762. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  763. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  764. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  765. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  766. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  767. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  768. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  769. nautobot/ipam/api/nested_serializers.py +0 -159
  770. nautobot/ipam/migrations/0029_ipam__prefix__add_parent.py +0 -31
  771. nautobot/ipam/migrations/0030_ipam__prefix__data_migration.py +0 -13
  772. nautobot/ipam/migrations/0031_ipam__ipaddress__add_parent.py +0 -41
  773. nautobot/ipam/migrations/0032_ipam__ipaddress__data_migration.py +0 -11
  774. nautobot/tenancy/api/nested_serializers.py +0 -31
  775. nautobot/users/api/nested_serializers.py +0 -67
  776. nautobot/virtualization/api/nested_serializers.py +0 -65
  777. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  778. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  779. {nautobot-2.0.0a3.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  780. {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 Device, Interface
19
- from nautobot.extras.models import RoleModelMixin, Status, StatusModel
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 VirtualMachine, VMInterface
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("dcim.Device", on_delete=models.CASCADE, related_name="vrf_assignments")
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
- return f"{self.vrf} [{self.device}] (rd: {self.rd}, name: {self.name})"
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, StatusModel, RoleModelMixin):
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.get_child_ips()])
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, StatusModel, RoleModelMixin):
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
- broadcast = VarbinaryIPField(null=False, db_index=True, help_text="IPv4 or IPv6 broadcast address")
907
- prefix_length = models.IntegerField(null=False, db_index=True, help_text="Length of the Network prefix, in bits.")
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, broadcast, and prefix_length.
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", "prefix_length") # address may be non-unique
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.broadcast = str(broadcast)
1005
- self.prefix_length = address.prefixlen
971
+ self.mask_length = address.prefixlen
1006
972
  self.ip_version = address.version
1007
973
 
1008
- def get_absolute_url(self):
1009
- return reverse("ipam:ipaddress", args=[self.pk])
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
- # TODO: update to work with interface M2M
1026
- # This attribute will have been set by `IPAddressForm.clean()` to indicate that the
1027
- # `primary_ip{version}` field on `self.assigned_object.parent` has been nullified but not yet saved.
1028
- primary_ip_unset_by_form = getattr(self, "_primary_ip_unset_by_form", False)
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.prefix_length is not None:
1087
- cidr = f"{self.host}/{self.prefix_length}"
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
- def validate_unique(self, exclude=None):
1172
- """
1173
- Check uniqueness on combination of `ip_address`, `interface` and `vm_interface` fields
1174
- and raise ValidationError if check failed.
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, StatusModel, RoleModelMixin):
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)