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
@@ -0,0 +1,255 @@
1
+ import copy
2
+ from importlib import import_module
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import re
7
+ import shlex
8
+ import subprocess
9
+
10
+ from django.apps import apps
11
+ from django.conf import settings
12
+ from django.contrib.admindocs.views import simplify_regex
13
+ from django.core.management.base import BaseCommand, CommandError
14
+ import jinja2
15
+
16
+
17
+ class Command(BaseCommand):
18
+ help = "Build the user interface for the Nautobot server environment and installed Nautobot Apps."
19
+
20
+ def add_arguments(self, parser):
21
+ parser.add_argument(
22
+ "--npm-install",
23
+ action="store_true",
24
+ dest="npm_install",
25
+ default=False,
26
+ help="Install UI packages.",
27
+ )
28
+ parser.add_argument(
29
+ "--no-render-apps",
30
+ action="store_false",
31
+ dest="render_apps",
32
+ default=True,
33
+ help="Do not render Nautobot App imports.",
34
+ )
35
+ parser.add_argument(
36
+ "--no-npm-build",
37
+ action="store_false",
38
+ dest="npm_build",
39
+ default=True,
40
+ help="Do not compile UI.",
41
+ )
42
+
43
+ def convert_django_url_regex_to_react_route_path(self, regex):
44
+ """
45
+ Converts a regular expression object to its equivalent react router path representation.
46
+
47
+ Args:
48
+ regex (re.Pattern): A regular expression object.
49
+
50
+ Example:
51
+ >>> pattern = re.compile('^other-models/(?P<pk>[^/.]+)/notes/$')
52
+ >>> convert_django_url_regex_to_react_route_path(pattern)
53
+ '/other-models/:pk/notes/'
54
+ """
55
+ pattern = str(regex.pattern)
56
+ path = simplify_regex(pattern).replace("<", ":").replace(">", "").replace("\\Z", "").replace("\\", "")
57
+ return path if path.endswith("/") else f"{path}/"
58
+
59
+ def get_app_component(self, file_path, route_name):
60
+ """
61
+ Obtains the view component for the given route name from the index.js file of the App.
62
+ This locates the `route_name` in the app configuration `routes_view_components` dict and
63
+ returns the View Component registered for the `route_name`.
64
+
65
+ Parameters:
66
+ file_path (str): The path to the JavaScript file to read from.
67
+ route_name (str): The name of the route to extract the view component for.
68
+
69
+ Returns:
70
+ str: The view component associated with the specified route name, or None if not found.
71
+
72
+ Example:
73
+ >>> get_app_component(file_path="/src/example_plugin/example_plugin/ui/index.js", route_name="example-plugin:examplemodel_list")
74
+ "ExampleModelListView"
75
+ """
76
+ try:
77
+ with open(file_path, "r") as f:
78
+ js_content = f.read()
79
+ except FileNotFoundError:
80
+ return None
81
+
82
+ # Construct a regular expression that matches the specified key and extracts the associated view component
83
+ pattern = rf'routes_view_components:\s*{{[^}}]*"{re.escape(route_name)}"\s*:\s*"([^"]+)"'
84
+ view_component_match = re.search(pattern, js_content)
85
+ return view_component_match[1] if view_component_match else None
86
+
87
+ def render_routes_imports(self, app_base_path, app_name, app_config):
88
+ """
89
+ Renders the imports for the React Router components of the App's URLs.
90
+
91
+ This method inspects the `urlpatterns` list of the `urls.py` module of the App and generates
92
+ a list of dictionaries representing the React Router components and their corresponding URL paths.
93
+
94
+ Args:
95
+ app_base_path (Path): The base path of the Django app.
96
+ app_name (str): The name of the Django app.
97
+ app_config (AppConfig): The configuration object of the Django app.
98
+
99
+ Returns:
100
+ List[Dict[str, str]]: A list of dictionaries representing the React Router components and their
101
+ corresponding URL paths.
102
+ Each dictionary has the following keys:
103
+ - "path": The URL path pattern.
104
+ - "component": The React component associated with the URL path.
105
+
106
+ Example:
107
+ >>> render_routes_imports("/src/example_plugin/", "example_plugin", <AppConfig instance>)
108
+ [
109
+ { "path": "example-plugin/", "component": "HomeView"},
110
+ { "path": "example-plugin/config/", "component": "ConfigView"},
111
+ ]
112
+ """
113
+ data = []
114
+ try:
115
+ module = import_module(f"{app_name}.urls")
116
+ base_url = app_config.base_url or app_config.label
117
+ for urlpattern in module.urlpatterns:
118
+ if component := self.get_app_component(
119
+ app_base_path / "ui/index.js",
120
+ f"{base_url}:{urlpattern.name}",
121
+ ):
122
+ path_regex = urlpattern.pattern.regex
123
+ url_path = self.convert_django_url_regex_to_react_route_path(path_regex)
124
+ data.append({"path": base_url + url_path, "component": component})
125
+ return data
126
+ except (AttributeError, ModuleNotFoundError):
127
+ # If an app does not include its url.py file or urls.py does not include a urlpatterns, skip.
128
+ return data
129
+
130
+ def render_app_imports(self):
131
+ """Render `app_imports.js`, update `jsconfig.json` to map to the path for each and render `app_routes.json` to register all app routes."""
132
+ self.stdout.write(self.style.WARNING(">>> Rendering Nautobot App imports..."))
133
+
134
+ ui_dir = settings.NAUTOBOT_UI_DIR
135
+
136
+ router_file_path = Path(ui_dir, "src", "router.js")
137
+ app_routes_file_path = Path(ui_dir, "src", "app_routes.json")
138
+ jsconfig_file_path = Path(ui_dir, "jsconfig.paths.json")
139
+ jsconfig_base_file_path = Path(ui_dir, "jsconfig-base.json")
140
+ app_routes = {}
141
+
142
+ with open(jsconfig_base_file_path, "r", encoding="utf-8") as base_config_file:
143
+ jsconfig = json.load(base_config_file)
144
+
145
+ # We're going to modify this list if apps don't have a `ui` directory.
146
+ enabled_apps = copy.copy(settings.PLUGINS)
147
+
148
+ for app_class_path in settings.PLUGINS:
149
+ app_name = app_class_path.split(".")[-1]
150
+ app_config = apps.get_app_config(app_name)
151
+ abs_app_path = Path(app_config.path).resolve()
152
+ abs_app_ui_path = abs_app_path / "ui"
153
+ app_path = Path(os.path.relpath(abs_app_path, ui_dir))
154
+ app_ui_path = app_path / "ui"
155
+ if app_routes_imports := self.render_routes_imports(abs_app_path, app_name, app_config):
156
+ app_routes[app_name] = app_routes_imports
157
+
158
+ # Assert that an App has a UI folder.
159
+ if not abs_app_ui_path.exists():
160
+ self.stdout.write(self.style.ERROR(f"- App {app_name!r} does not publish a UI; Skipping..."))
161
+ enabled_apps.remove(app_class_path)
162
+ continue
163
+ self.stdout.write(self.style.SUCCESS(f"- App {app_name!r} imported"))
164
+
165
+ jsconfig["compilerOptions"]["paths"][f"@{app_name}/*"] = [f"{app_ui_path}/*"]
166
+
167
+ with open(jsconfig_file_path, "w", encoding="utf-8") as generated_config_file:
168
+ json.dump(jsconfig, generated_config_file, indent=4)
169
+
170
+ with open(app_routes_file_path, "w", encoding="utf-8") as app_routes_file:
171
+ json.dump(app_routes, app_routes_file, indent=4)
172
+
173
+ app_imports_final_file_path = Path(ui_dir, "src", "app_imports.js")
174
+ environment = jinja2.sandbox.SandboxedEnvironment(loader=jinja2.FileSystemLoader(ui_dir))
175
+ template = environment.get_template("app_imports.js.j2")
176
+ content = template.render(apps=enabled_apps)
177
+
178
+ with open(app_imports_final_file_path, "w", encoding="utf-8") as generated_import_file:
179
+ generated_import_file.write(content)
180
+
181
+ # Touch the router to attempt to trigger a server reload.
182
+ Path(router_file_path).touch()
183
+
184
+ def run_command(self, command, message, cwd=settings.NAUTOBOT_UI_DIR):
185
+ """
186
+ Run a `command`, displaying `message` and exit. This splits it for you and runs it.
187
+
188
+ Args:
189
+ command (str): The command to execute
190
+ message (str): Message to display when command is executed
191
+ """
192
+ self.stdout.write(self.style.WARNING(message))
193
+ self.stdout.write(f"Running '{command}' in '{cwd}'...")
194
+
195
+ try:
196
+ result = subprocess.run(
197
+ shlex.split(command),
198
+ check=False,
199
+ cwd=cwd,
200
+ env={**os.environ.copy()},
201
+ encoding="utf-8",
202
+ capture_output=True,
203
+ )
204
+ except FileNotFoundError as err:
205
+ raise CommandError(f"'{command}' failed with error: {err}")
206
+
207
+ if result.returncode:
208
+ self.stdout.write(self.style.NOTICE(result.stdout))
209
+ self.stderr.write(self.style.ERROR(result.stderr))
210
+ raise CommandError(f"'{command}' failed with exit code {result.returncode}")
211
+
212
+ def handle(self, *args, **options):
213
+ verbosity = options["verbosity"]
214
+
215
+ # NAUTOBOT_DEBUG is also set here so that `console.log` isn't suppressed when we want
216
+ # verbosity.
217
+ if verbosity > 1:
218
+ os.environ["NAUTOBOT_DEBUG"] = "1"
219
+
220
+ verbosity_map = {
221
+ 0: "silent",
222
+ 1: "notice",
223
+ 2: "info",
224
+ 3: "silly", # MAXIMUM OVERDEBUG
225
+ }
226
+ loglevel = f"--loglevel {verbosity_map[verbosity]}"
227
+
228
+ if not os.path.exists(Path(settings.NAUTOBOT_UI_DIR)):
229
+ os.makedirs(Path(settings.NAUTOBOT_UI_DIR))
230
+
231
+ self.run_command(
232
+ f"cp -r {Path(settings.BASE_DIR, 'ui')}/. {Path(settings.NAUTOBOT_UI_DIR)}",
233
+ ">>> Copying UI source files...",
234
+ )
235
+
236
+ # Generate `app_imports.js`
237
+ if options["render_apps"]:
238
+ self.render_app_imports()
239
+
240
+ # Run `npm install` and keep it silent by default.
241
+ if options["npm_install"]:
242
+ args = f"install {loglevel} --no-progress"
243
+ self.run_command(f"npm {args}", ">>> Installing Nautobot UI dependencies...")
244
+
245
+ # Run `npm build` and keep it silent by default.
246
+ if options["npm_build"]:
247
+ args = f"run build {loglevel}"
248
+ self.run_command(f"npm {args}", ">>> Compiling Nautobot UI packages...")
249
+ self.run_command(
250
+ f"cp {Path(settings.NAUTOBOT_UI_DIR, 'build' ,'asset-manifest.json')} "
251
+ f"{Path(settings.NAUTOBOT_UI_DIR, 'build' , 'static', 'asset-manifest.json')}",
252
+ ">>> Copying built manifest...",
253
+ )
254
+
255
+ self.stdout.write(self.style.SUCCESS(">>> Nautobot UI build complete! 🎉"))
@@ -185,15 +185,16 @@ Type 'yes' to continue, or 'no' to cancel: """
185
185
  call_command("flush", "--no-input")
186
186
 
187
187
  if options["cache_test_fixtures"] and os.path.exists(options["fixture_file"]):
188
+ self.stdout.write(self.style.WARNING(f"Loading factory data from file {options['fixture_file']}"))
188
189
  call_command("loaddata", options["fixture_file"])
189
190
  else:
190
191
  self._generate_factory_data(options["seed"])
191
192
 
192
193
  if options["cache_test_fixtures"]:
194
+ self.stdout.write(self.style.WARNING(f"Saving factory data to file {options['fixture_file']}"))
195
+
193
196
  call_command(
194
197
  "dumpdata",
195
- "--natural-foreign",
196
- "--natural-primary",
197
198
  indent=2,
198
199
  format="json",
199
200
  exclude=["contenttypes", "auth.permission", "extras.job", "extras.customfield"],
@@ -11,10 +11,10 @@ This will run the following management commands with default settings, in order:
11
11
 
12
12
  - migrate
13
13
  - trace_paths
14
+ - build_ui --npm-install
14
15
  - collectstatic
15
16
  - remove_stale_contenttypes
16
17
  - clearsessions
17
- - invalidate all
18
18
  """
19
19
 
20
20
 
@@ -28,6 +28,13 @@ class Command(BaseCommand):
28
28
  return parser
29
29
 
30
30
  def add_arguments(self, parser):
31
+ parser.add_argument(
32
+ "--no-build-ui",
33
+ action="store_false",
34
+ dest="build_ui",
35
+ default=True,
36
+ help="Do not automatically build the user interface.",
37
+ )
31
38
  parser.add_argument(
32
39
  "--no-clearsessions",
33
40
  action="store_false",
@@ -42,13 +49,6 @@ class Command(BaseCommand):
42
49
  default=True,
43
50
  help="Do not automatically collect static files into a single location.",
44
51
  )
45
- parser.add_argument(
46
- "--no-invalidate-all",
47
- action="store_false",
48
- dest="invalidate_all",
49
- default=True,
50
- help="Do not automatically invalidate cache for entire application.",
51
- )
52
52
  parser.add_argument(
53
53
  "--no-migrate",
54
54
  action="store_false",
@@ -74,41 +74,41 @@ class Command(BaseCommand):
74
74
  def handle(self, *args, **options):
75
75
  # Run migrate
76
76
  if options.get("migrate"):
77
- print("Performing database migrations...")
77
+ self.stdout.write("Performing database migrations...")
78
78
  call_command(
79
79
  "migrate",
80
80
  interactive=False,
81
81
  traceback=options["traceback"],
82
82
  verbosity=options["verbosity"],
83
83
  )
84
- print()
84
+ self.stdout.write()
85
85
 
86
86
  # Run trace_paths
87
87
  if options.get("trace_paths"):
88
- print("Generating cable paths...")
88
+ self.stdout.write("Generating cable paths...")
89
89
  call_command("trace_paths", no_input=True)
90
- print()
90
+ self.stdout.write()
91
+
92
+ # Run build
93
+ if options.get("build_ui"):
94
+ self.stdout.write("Building user interface...")
95
+ call_command("build_ui", npm_install=True)
96
+ self.stdout.write()
91
97
 
92
98
  # Run collectstatic
93
99
  if options.get("collectstatic"):
94
- print("Collecting static files...")
100
+ self.stdout.write("Collecting static files...")
95
101
  call_command("collectstatic", interactive=False)
96
- print()
102
+ self.stdout.write()
97
103
 
98
104
  # Run remove_stale_contenttypes
99
105
  if options.get("remove_stale_contenttypes"):
100
- print("Removing stale content types...")
106
+ self.stdout.write("Removing stale content types...")
101
107
  call_command("remove_stale_contenttypes", interactive=False)
102
- print()
108
+ self.stdout.write()
103
109
 
104
110
  # Run clearsessions
105
111
  if options.get("clearsessions"):
106
- print("Removing expired sessions...")
112
+ self.stdout.write("Removing expired sessions...")
107
113
  call_command("clearsessions")
108
- print()
109
-
110
- # Run invalidate all
111
- if options.get("invalidate_all"):
112
- print("Invalidating cache...")
113
- call_command("invalidate", "all")
114
- print()
114
+ self.stdout.write()
@@ -1,11 +1,13 @@
1
1
  import uuid
2
2
 
3
3
  from django.db import models
4
+ from django.urls import NoReverseMatch, reverse
4
5
  from django.utils.functional import classproperty
5
6
 
6
7
  from nautobot.core.models.managers import BaseManager
7
8
  from nautobot.core.models.querysets import RestrictedQuerySet
8
9
  from nautobot.core.models.utils import construct_natural_key_slug
10
+ from nautobot.core.utils.lookup import get_route_for_model
9
11
 
10
12
 
11
13
  class BaseModel(models.Model):
@@ -37,6 +39,29 @@ class BaseModel(models.Model):
37
39
  """
38
40
  return not self._state.adding
39
41
 
42
+ def get_absolute_url(self, api=False):
43
+ """
44
+ Return the canonical URL for this object in either the UI or the REST API.
45
+ """
46
+
47
+ # Iterate the pk-like fields and try to get a URL, or return None.
48
+ fields = ["pk", "slug"] # TODO: Eventually all PKs
49
+ actions = ["retrieve", "detail", ""] # TODO: Eventually all retrieve
50
+
51
+ for field in fields:
52
+ if not hasattr(self, field):
53
+ continue
54
+
55
+ for action in actions:
56
+ route = get_route_for_model(self, action, api=api)
57
+
58
+ try:
59
+ return reverse(route, kwargs={field: getattr(self, field)})
60
+ except NoReverseMatch:
61
+ continue
62
+
63
+ return AttributeError(f"Cannot find a URL for {self} ({self._meta.app_label}.{self._meta.model_name})")
64
+
40
65
  class Meta:
41
66
  abstract = True
42
67
 
@@ -48,7 +73,7 @@ class BaseModel(models.Model):
48
73
  which in effect enforces model validation prior to saving the instance, without having
49
74
  to manually make these calls seperately. This is a slight departure from Django norms,
50
75
  but is intended to offer an optional, simplified interface for performing this common
51
- workflow. The intended use is for user defined Jobs and scripts run via the `nautobot-server nbshell`
76
+ workflow. The intended use is for user defined Jobs run via the `nautobot-server nbshell`
52
77
  command.
53
78
  """
54
79
  self.full_clean()
@@ -8,9 +8,11 @@ from django.db import models
8
8
  from django.utils.text import slugify
9
9
  from django_extensions.db.fields import AutoSlugField as _AutoSlugField
10
10
  from netaddr import AddrFormatError, EUI, mac_unix_expanded
11
+ from taggit.managers import TaggableManager
11
12
 
12
13
  from nautobot.core.forms import fields, widgets
13
14
  from nautobot.core.models import ordering
15
+ from nautobot.core.models.managers import TagsManager
14
16
 
15
17
 
16
18
  class mac_unix_expanded_uppercase(mac_unix_expanded):
@@ -184,10 +186,6 @@ class ForeignKeyLimitedByContentTypes(ForeignKeyWithAutoRelatedName):
184
186
  - the content-type to which the field is attached (e.g. `dcim.device`)
185
187
  """
186
188
 
187
- def __init__(self, **kwargs):
188
- kwargs.setdefault("null", True)
189
- super().__init__(**kwargs)
190
-
191
189
  def get_limit_choices_to(self):
192
190
  return {"content_types": ContentType.objects.get_for_model(self.model)}
193
191
 
@@ -323,7 +321,7 @@ class JSONArrayField(models.JSONField):
323
321
  # Assume we're deserializing
324
322
  vals = json.loads(value)
325
323
  value = [self.base_field.to_python(val) for val in vals]
326
- except json.JSONDecodeError as e:
324
+ except (TypeError, json.JSONDecodeError) as e:
327
325
  raise exceptions.ValidationError(e)
328
326
  return value
329
327
 
@@ -370,3 +368,24 @@ class JSONArrayField(models.JSONField):
370
368
  **kwargs,
371
369
  }
372
370
  )
371
+
372
+
373
+ class TagsField(TaggableManager):
374
+ """Override FormField method on taggit.managers.TaggableManager to match the Nautobot UI."""
375
+
376
+ def __init__(self, *args, **kwargs):
377
+ from nautobot.extras.models.tags import TaggedItem
378
+
379
+ kwargs.setdefault("through", TaggedItem)
380
+ kwargs.setdefault("manager", TagsManager)
381
+ kwargs.setdefault("ordering", ["name"])
382
+ super().__init__(*args, **kwargs)
383
+
384
+ def formfield(self, form_class=fields.DynamicModelMultipleChoiceField, **kwargs):
385
+ from nautobot.extras.models.tags import Tag
386
+
387
+ queryset = Tag.objects.get_for_model(self.model)
388
+ kwargs.setdefault("queryset", queryset)
389
+ kwargs.setdefault("required", False)
390
+ kwargs.setdefault("query_params", {"content_types": self.model._meta.label_lower})
391
+ return super().formfield(form_class=form_class, **kwargs)
@@ -1,13 +1,11 @@
1
1
  import logging
2
2
 
3
- from taggit.managers import TaggableManager, _TaggableManager
4
-
5
3
  from nautobot.extras.models.change_logging import ChangeLoggedModel
6
4
  from nautobot.extras.models.customfields import CustomFieldModel
7
5
  from nautobot.extras.models.mixins import DynamicGroupMixin, NotesMixin
8
6
  from nautobot.extras.models.relationships import RelationshipModel
9
- from nautobot.extras.models.tags import TaggedItem
10
7
  from nautobot.core.models import BaseModel
8
+ from nautobot.core.models.fields import TagsField
11
9
 
12
10
 
13
11
  logger = logging.getLogger(__name__)
@@ -30,44 +28,6 @@ class OrganizationalModel(
30
28
  abstract = True
31
29
 
32
30
 
33
- # 2.0 TODO: remove this, force migration to the newer django-taggit API.
34
- class _NautobotTaggableManager(_TaggableManager):
35
- """Extend _TaggableManager to work around a breaking API change between django-taggit 1.x and 2.x.
36
-
37
- This is a bit confusing, as there's also a related `TaggableManager` class as well.
38
- `TaggableManager` is the *model field* (subclass of `models.fields.related.RelatedField`),
39
- while `_TaggableManager` is the *associated manager* (subclass of `models.Manager`).
40
-
41
- For `TaggableManager`, we chose to monkey-patch rather than subclass to override its `formfield` method;
42
- replacing it with a subclass would create database migrations for every PrimaryModel with a `tags` field.
43
- In 2.0 we'll want to bite the bullet and make the cutover (#1633).
44
-
45
- For `_TaggableManager`, we can subclass rather than monkey-patching because replacing it *doesn't* require
46
- a database migration, and this is cleaner than a monkey-patch.
47
- """
48
-
49
- def set(self, *tags, through_defaults=None, **kwargs):
50
- """
51
- Patch model.tags.set() to be backwards-compatible with django-taggit 1.x and forward-compatible with later.
52
-
53
- Both of these approaches are supported:
54
-
55
- - tags.set("tag 1", "tag 2") # django-taggit 1.x
56
- - tags.set(["tag 1", "tag 2"]) # django-taggit 2.x and later
57
- """
58
- if len(tags) == 1 and not isinstance(tags[0], (self.through.tag_model(), str)):
59
- # taggit 2.x+ style, i.e. `set([tag, tag, tag])`
60
- tags = tags[0]
61
- else:
62
- # taggit 1.x style, i.e. `set(tag, tag, tag)`
63
- # Note: logger.warning() only supports a `stacklevel` parameter in Python 3.8 and later
64
- tags_unpacked = ", ".join([repr(tag) for tag in tags])
65
- tags_list = list(tags)
66
- message = "Deprecated `tags.set(%s)` was called, please change to `tags.set(%s)` instead"
67
- logger.warning(message, tags_unpacked, tags_list, stacklevel=2)
68
- return super().set(tags, through_defaults=through_defaults, **kwargs)
69
-
70
-
71
31
  class PrimaryModel(BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipModel, DynamicGroupMixin, NotesMixin):
72
32
  """
73
33
  Base abstract model for all primary models.
@@ -78,7 +38,7 @@ class PrimaryModel(BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipM
78
38
  tangible or logical resources on the network, or within the organization.
79
39
  """
80
40
 
81
- tags = TaggableManager(through=TaggedItem, manager=_NautobotTaggableManager, ordering=["name"])
41
+ tags = TagsField()
82
42
 
83
43
  class Meta:
84
44
  abstract = True
@@ -1,4 +1,5 @@
1
1
  from django.db.models import Manager
2
+ from taggit.managers import _TaggableManager
2
3
 
3
4
 
4
5
  class BaseManager(Manager):
@@ -43,3 +44,7 @@ class BaseManager(Manager):
43
44
  raise self.model.DoesNotExist() from exc
44
45
 
45
46
  return self.get(**kwargs)
47
+
48
+
49
+ class TagsManager(_TaggableManager, BaseManager):
50
+ """Manager class for model 'tags' fields."""
@@ -1,7 +1,6 @@
1
1
  from django.contrib.contenttypes.models import ContentType
2
2
  from django.db import models
3
3
  from django.db.models import Q
4
- from django.urls import reverse
5
4
 
6
5
  from nautobot.core.choices import ColorChoices
7
6
  from nautobot.core.models import BaseManager, BaseModel
@@ -66,7 +65,6 @@ class NameColorContentTypesModel(
66
65
 
67
66
  objects = BaseManager.from_queryset(ContentTypeRelatedQuerySet)()
68
67
 
69
- csv_headers = ["name", "color", "content_types", "description"]
70
68
  clone_fields = ["color", "content_types"]
71
69
 
72
70
  class Meta:
@@ -76,17 +74,5 @@ class NameColorContentTypesModel(
76
74
  def __str__(self):
77
75
  return self.name
78
76
 
79
- def get_absolute_url(self):
80
- ct = f"{self._meta.app_label}:{self._meta.model_name}"
81
- return reverse(ct, args=[self.pk])
82
-
83
77
  def get_content_types(self):
84
78
  return ",".join(f"{ct.app_label}.{ct.model}" for ct in self.content_types.all())
85
-
86
- def to_csv(self):
87
- return (
88
- self.name,
89
- self.color,
90
- self.get_content_types(),
91
- self.description,
92
- )
@@ -1,3 +1,5 @@
1
+ from django.core.cache import cache
2
+
1
3
  from tree_queries.models import TreeNode
2
4
  from tree_queries.query import TreeManager as TreeManager_
3
5
  from tree_queries.query import TreeQuerySet as TreeQuerySet_
@@ -41,16 +43,24 @@ class TreeModel(TreeNode):
41
43
 
42
44
  @property
43
45
  def display(self):
44
- """By default, TreeModels display their full ancestry for clarity."""
46
+ """
47
+ By default, TreeModels display their full ancestry for clarity.
48
+
49
+ As this is an expensive thing to calculate, we cache it for a few seconds in the case of repeated lookups.
50
+ """
45
51
  if not hasattr(self, "name"):
46
52
  raise NotImplementedError("default TreeModel.display implementation requires a `name` attribute!")
47
- display_str = ""
53
+ cache_key = f"{self.__class__.__name__}.{self.id}.display"
54
+ display_str = cache.get(cache_key, "")
55
+ if display_str:
56
+ return display_str
48
57
  try:
49
- for ancestor in self.ancestors():
50
- display_str += ancestor.name + " → "
58
+ if self.parent is not None:
59
+ display_str = self.parent.display + " → "
51
60
  except self.DoesNotExist:
52
61
  # Expected to occur at times during bulk-delete operations
53
62
  pass
54
63
  finally:
55
64
  display_str += self.name
65
+ cache.set(cache_key, display_str, 5)
56
66
  return display_str # pylint: disable=lost-exception
@@ -1,12 +1,12 @@
1
- from itertools import count, groupby
2
1
  import json
2
+
3
+ from itertools import count, groupby
3
4
  from urllib.parse import quote_plus, unquote_plus
4
5
 
5
6
  from django.apps import apps
6
7
  from django.core.exceptions import FieldDoesNotExist
7
8
  from django.core.serializers import serialize
8
9
  from django.utils.tree import Node
9
- from taggit.managers import _TaggableManager
10
10
 
11
11
  from nautobot.core.models.constants import NATURAL_KEY_SLUG_SEPARATOR
12
12
 
@@ -35,10 +35,9 @@ def is_taggable(obj):
35
35
  """
36
36
  Return True if the instance can have Tags assigned to it; False otherwise.
37
37
  """
38
- if hasattr(obj, "tags"):
39
- if issubclass(obj.tags.__class__, _TaggableManager):
40
- return True
41
- return False
38
+ from nautobot.core.models.managers import TagsManager
39
+
40
+ return hasattr(obj, "tags") and isinstance(obj.tags, TagsManager)
42
41
 
43
42
 
44
43
  def pretty_print_query(query):