nautobot 2.0.0a2__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 (1029) hide show
  1. nautobot/__init__.py +1 -5
  2. nautobot/apps/api.py +6 -8
  3. nautobot/apps/forms.py +0 -2
  4. nautobot/apps/ui.py +0 -8
  5. nautobot/circuits/api/serializers.py +9 -119
  6. nautobot/circuits/api/urls.py +1 -1
  7. nautobot/circuits/api/views.py +0 -1
  8. nautobot/circuits/choices.py +0 -2
  9. nautobot/circuits/filters.py +7 -6
  10. nautobot/circuits/forms.py +3 -73
  11. nautobot/circuits/migrations/0001_initial_part_1.py +0 -1
  12. nautobot/circuits/migrations/0002_initial_part_2.py +0 -1
  13. nautobot/circuits/migrations/0003_auto_slug.py +0 -1
  14. nautobot/circuits/migrations/0004_increase_provider_account_length.py +0 -1
  15. nautobot/circuits/migrations/0005_providernetwork.py +0 -1
  16. nautobot/circuits/migrations/0006_cache_circuit_terminations.py +0 -1
  17. nautobot/circuits/migrations/0007_circuitterminations_primary_model.py +0 -1
  18. nautobot/circuits/migrations/0008_add_natural_indexing.py +0 -1
  19. nautobot/circuits/migrations/0009_circuittermination_location.py +0 -1
  20. nautobot/circuits/migrations/0010_rename_foreign_keys_and_related_names.py +0 -1
  21. nautobot/circuits/migrations/0011_remove_site_foreign_key_from_circuit_termination_class.py +0 -1
  22. nautobot/circuits/migrations/0012_created_datetime.py +0 -1
  23. nautobot/circuits/migrations/0013_alter_circuittermination__path.py +0 -1
  24. nautobot/circuits/migrations/0014_related_name_changes.py +1 -2
  25. nautobot/circuits/migrations/0015_remove_circuittype_provider_slug.py +20 -0
  26. nautobot/circuits/migrations/0016_tagsfield.py +34 -0
  27. nautobot/circuits/migrations/0017_fixup_null_statuses.py +22 -0
  28. nautobot/circuits/migrations/0018_status_nonnullable.py +22 -0
  29. nautobot/circuits/models.py +3 -93
  30. nautobot/circuits/navigation.py +14 -69
  31. nautobot/circuits/signals.py +0 -2
  32. nautobot/circuits/tables.py +42 -5
  33. nautobot/circuits/templates/circuits/circuit_retrieve.html +1 -1
  34. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -1
  35. nautobot/circuits/templates/circuits/circuittype_retrieve.html +1 -1
  36. nautobot/circuits/templates/circuits/provider_create.html +0 -1
  37. nautobot/circuits/templates/circuits/provider_retrieve.html +1 -1
  38. nautobot/circuits/tests/integration/test_relationships.py +13 -16
  39. nautobot/circuits/tests/test_api.py +13 -43
  40. nautobot/circuits/tests/test_filters.py +20 -15
  41. nautobot/circuits/tests/test_models.py +7 -3
  42. nautobot/circuits/tests/test_views.py +57 -67
  43. nautobot/circuits/views.py +18 -9
  44. nautobot/core/api/__init__.py +8 -2
  45. nautobot/core/api/authentication.py +0 -3
  46. nautobot/core/api/fields.py +15 -6
  47. nautobot/core/api/filter_backends.py +3 -2
  48. nautobot/core/api/metadata.py +237 -30
  49. nautobot/core/api/mixins.py +94 -0
  50. nautobot/core/api/pagination.py +3 -3
  51. nautobot/core/api/parsers.py +154 -0
  52. nautobot/core/api/renderers.py +153 -2
  53. nautobot/core/api/schema.py +47 -3
  54. nautobot/core/api/serializers.py +377 -37
  55. nautobot/core/api/urls.py +11 -3
  56. nautobot/core/api/utils.py +174 -2
  57. nautobot/core/api/versioning.py +32 -10
  58. nautobot/core/api/views.py +266 -75
  59. nautobot/core/apps/__init__.py +138 -221
  60. nautobot/core/celery/__init__.py +112 -41
  61. nautobot/core/celery/backends.py +19 -13
  62. nautobot/core/celery/control.py +46 -0
  63. nautobot/core/celery/encoders.py +53 -0
  64. nautobot/core/celery/log.py +38 -0
  65. nautobot/core/celery/schedulers.py +23 -4
  66. nautobot/core/celery/task.py +1 -16
  67. nautobot/core/checks.py +0 -27
  68. nautobot/core/choices.py +21 -113
  69. nautobot/core/{cli.py → cli/__init__.py} +1 -2
  70. nautobot/core/cli/__main__.py +3 -0
  71. nautobot/core/constants.py +25 -43
  72. nautobot/core/context_processors.py +12 -0
  73. nautobot/core/filters.py +2 -2
  74. nautobot/core/forms/__init__.py +0 -4
  75. nautobot/core/forms/fields.py +39 -68
  76. nautobot/core/forms/forms.py +27 -27
  77. nautobot/core/forms/utils.py +7 -59
  78. nautobot/core/forms/widgets.py +0 -1
  79. nautobot/core/graphql/__init__.py +2 -2
  80. nautobot/core/graphql/schema.py +4 -27
  81. nautobot/core/jobs/__init__.py +75 -0
  82. nautobot/core/management/commands/build_ui.py +255 -0
  83. nautobot/core/management/commands/celery.py +0 -1
  84. nautobot/core/management/commands/generate_test_data.py +18 -13
  85. nautobot/core/management/commands/post_upgrade.py +24 -24
  86. nautobot/core/management/commands/validate_models.py +0 -1
  87. nautobot/core/middleware.py +0 -1
  88. nautobot/core/models/__init__.py +26 -1
  89. nautobot/core/models/fields.py +24 -5
  90. nautobot/core/models/generics.py +2 -46
  91. nautobot/core/models/managers.py +5 -0
  92. nautobot/core/models/name_color_content_types.py +1 -19
  93. nautobot/core/models/tree_queries.py +14 -4
  94. nautobot/core/models/utils.py +9 -10
  95. nautobot/core/models/validators.py +17 -8
  96. nautobot/core/releases.py +8 -10
  97. nautobot/core/settings.py +81 -53
  98. nautobot/core/tables.py +5 -5
  99. nautobot/core/tasks.py +4 -7
  100. nautobot/core/templates/base.html +1 -49
  101. nautobot/core/templates/base_django.html +49 -0
  102. nautobot/core/templates/base_react.html +55 -0
  103. nautobot/core/templates/buttons/export.html +6 -4
  104. nautobot/core/templates/generic/object_bulk_create.html +10 -21
  105. nautobot/core/templates/generic/object_list.html +4 -1
  106. nautobot/core/templates/generic/object_retrieve_plugin_full_width.html +3 -0
  107. nautobot/core/templates/inc/footer.html +1 -0
  108. nautobot/core/templates/inc/javascript.html +0 -14
  109. nautobot/core/templates/inc/nav_menu.html +28 -33
  110. nautobot/core/templates/inc/object_details_advanced_panel.html +13 -0
  111. nautobot/core/templates/inc/relationships_table_rows.html +2 -2
  112. nautobot/core/templates/nautobot_config.py.j2 +8 -25
  113. nautobot/core/templates/plugin_template/__init__.py-tpl +1 -2
  114. nautobot/core/templates/rest_framework/api.html +8 -0
  115. nautobot/core/templatetags/buttons.py +32 -29
  116. nautobot/core/templatetags/helpers.py +1 -1
  117. nautobot/core/testing/__init__.py +47 -44
  118. nautobot/core/testing/api.py +365 -47
  119. nautobot/core/testing/filters.py +12 -7
  120. nautobot/core/testing/integration.py +1 -1
  121. nautobot/core/testing/migrations.py +2 -0
  122. nautobot/core/testing/mixins.py +22 -12
  123. nautobot/core/testing/schema.py +2 -1
  124. nautobot/core/testing/views.py +28 -51
  125. nautobot/core/tests/integration/test_filters.py +17 -8
  126. nautobot/core/tests/integration/test_navbar.py +11 -34
  127. nautobot/core/tests/integration/test_plugin_navbar.py +9 -103
  128. nautobot/core/tests/nautobot_config.py +2 -3
  129. nautobot/core/tests/runner.py +0 -1
  130. nautobot/core/tests/test_api.py +290 -24
  131. nautobot/core/tests/test_authentication.py +57 -14
  132. nautobot/core/tests/test_checks.py +0 -7
  133. nautobot/core/tests/test_choices.py +0 -1
  134. nautobot/core/tests/test_filters.py +117 -110
  135. nautobot/core/tests/test_forms.py +47 -110
  136. nautobot/core/tests/test_graphql.py +158 -135
  137. nautobot/core/tests/test_logging.py +4 -1
  138. nautobot/core/tests/test_managers.py +3 -5
  139. nautobot/core/tests/test_models.py +2 -0
  140. nautobot/core/tests/test_ordering.py +0 -2
  141. nautobot/core/tests/test_paginator.py +3 -1
  142. nautobot/core/tests/test_releases.py +12 -12
  143. nautobot/core/tests/test_templatetags_helpers.py +7 -4
  144. nautobot/core/tests/test_utils.py +112 -78
  145. nautobot/core/tests/test_views.py +12 -17
  146. nautobot/core/tests/test_views_utils.py +6 -9
  147. nautobot/core/utils/data.py +17 -0
  148. nautobot/core/utils/deprecation.py +13 -20
  149. nautobot/core/utils/filtering.py +53 -9
  150. nautobot/core/utils/git.py +12 -4
  151. nautobot/core/utils/lookup.py +3 -1
  152. nautobot/core/utils/requests.py +23 -116
  153. nautobot/core/views/__init__.py +1 -2
  154. nautobot/core/views/generic.py +131 -119
  155. nautobot/core/views/mixins.py +53 -62
  156. nautobot/core/views/paginator.py +0 -1
  157. nautobot/core/views/renderers.py +14 -12
  158. nautobot/core/views/utils.py +87 -4
  159. nautobot/dcim/api/serializers.py +160 -672
  160. nautobot/dcim/api/urls.py +1 -1
  161. nautobot/dcim/api/views.py +7 -46
  162. nautobot/dcim/choices.py +2 -25
  163. nautobot/dcim/elevations.py +0 -1
  164. nautobot/dcim/factory.py +15 -4
  165. nautobot/dcim/filters/__init__.py +42 -13
  166. nautobot/dcim/form_mixins.py +1 -27
  167. nautobot/dcim/forms.py +58 -797
  168. nautobot/dcim/management/commands/trace_paths.py +0 -1
  169. nautobot/dcim/migrations/0001_initial_part_1.py +0 -1
  170. nautobot/dcim/migrations/0002_initial_part_2.py +0 -1
  171. nautobot/dcim/migrations/0003_initial_part_3.py +0 -1
  172. nautobot/dcim/migrations/0004_initial_part_4.py +0 -1
  173. nautobot/dcim/migrations/0005_device_local_context_schema.py +0 -1
  174. nautobot/dcim/migrations/0006_auto_slug.py +0 -1
  175. nautobot/dcim/migrations/0007_device_secrets_group.py +0 -1
  176. nautobot/dcim/migrations/0008_increase_all_serial_lengths.py +0 -1
  177. nautobot/dcim/migrations/0009_add_natural_indexing.py +0 -1
  178. nautobot/dcim/migrations/0010_interface_status.py +0 -1
  179. nautobot/dcim/migrations/0011_interface_status_data_migration.py +0 -1
  180. nautobot/dcim/migrations/0012_interface_parent_bridge.py +0 -1
  181. nautobot/dcim/migrations/0013_location_location_type.py +0 -1
  182. nautobot/dcim/migrations/0014_location_status_data_migration.py +0 -1
  183. nautobot/dcim/migrations/0015_device_components__changeloggedmodel.py +0 -1
  184. nautobot/dcim/migrations/0016_device_components__timestamp_data_migration.py +0 -1
  185. nautobot/dcim/migrations/0017_locationtype_nestable.py +0 -1
  186. nautobot/dcim/migrations/0018_device_redundancy_group.py +0 -1
  187. nautobot/dcim/migrations/0019_device_redundancy_group_data_migration.py +0 -1
  188. nautobot/dcim/migrations/0020_move_site_fields_to_location_model.py +0 -1
  189. nautobot/dcim/migrations/0021_mptt_to_tree_queries.py +0 -1
  190. nautobot/dcim/migrations/0022_interface_mac_address_data_migration.py +0 -1
  191. nautobot/dcim/migrations/0023_alter_interface_mac_address.py +0 -1
  192. nautobot/dcim/migrations/0024_alter_device_and_rack_role_add_new_role.py +2 -2
  193. nautobot/dcim/migrations/0025_device_and_rack_roles_data_migrations.py +19 -14
  194. nautobot/dcim/migrations/0026_rename_device_and_rack_role.py +0 -1
  195. nautobot/dcim/migrations/0027_remove_device_role_and_rack_role.py +1 -2
  196. nautobot/dcim/migrations/0028_rename_foreignkey_fields.py +1 -2
  197. nautobot/dcim/migrations/0029_add_tree_managers_and_foreign_keys_pre_data_migration.py +0 -1
  198. nautobot/dcim/migrations/0030_migrate_region_and_site_data_to_locations.py +2 -2
  199. nautobot/dcim/migrations/0031_rename_path_end_point_related_name.py +0 -1
  200. nautobot/dcim/migrations/0032_remove_site_foreign_key_from_dcim_models.py +0 -1
  201. nautobot/dcim/migrations/0033_created_datetime.py +0 -1
  202. nautobot/dcim/migrations/0034_fixup_fks_and_related_names.py +0 -1
  203. nautobot/dcim/migrations/0035_related_name_changes.py +1 -2
  204. nautobot/dcim/migrations/0036_remove_region_and_site.py +1 -2
  205. nautobot/dcim/migrations/0037_interface_ip_addresses_m2m.py +0 -1
  206. nautobot/dcim/migrations/0038_alter_location_managers.py +0 -1
  207. nautobot/dcim/migrations/0039_remove_slug.py +24 -0
  208. nautobot/dcim/migrations/0040_tagsfield.py +109 -0
  209. nautobot/dcim/migrations/0041_ipam__namespaces.py +25 -0
  210. nautobot/dcim/migrations/0042_fixup_null_statuses.py +51 -0
  211. nautobot/dcim/migrations/0043_status_nonnullable.py +72 -0
  212. nautobot/dcim/models/cables.py +4 -35
  213. nautobot/dcim/models/device_component_templates.py +7 -2
  214. nautobot/dcim/models/device_components.py +26 -203
  215. nautobot/dcim/models/devices.py +30 -152
  216. nautobot/dcim/models/locations.py +3 -64
  217. nautobot/dcim/models/power.py +3 -51
  218. nautobot/dcim/models/racks.py +7 -86
  219. nautobot/dcim/navigation.py +141 -467
  220. nautobot/dcim/signals.py +0 -2
  221. nautobot/dcim/tables/devices.py +8 -5
  222. nautobot/dcim/tables/devicetypes.py +1 -1
  223. nautobot/dcim/tables/locations.py +2 -2
  224. nautobot/dcim/tables/power.py +2 -2
  225. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -0
  226. nautobot/dcim/templates/dcim/device.html +15 -4
  227. nautobot/dcim/templates/dcim/device_edit.html +6 -0
  228. nautobot/dcim/templates/dcim/deviceredundancygroup_create.html +0 -1
  229. nautobot/dcim/templates/dcim/devicetype.html +2 -2
  230. nautobot/dcim/templates/dcim/interface.html +4 -0
  231. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -0
  232. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  233. nautobot/dcim/templates/dcim/location.html +16 -1
  234. nautobot/dcim/templates/dcim/locationtype.html +15 -0
  235. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -0
  236. nautobot/dcim/templates/dcim/rackgroup.html +0 -12
  237. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  238. nautobot/dcim/tests/test_api.py +202 -130
  239. nautobot/dcim/tests/test_cablepaths.py +47 -42
  240. nautobot/dcim/tests/test_filters.py +156 -134
  241. nautobot/dcim/tests/test_forms.py +12 -213
  242. nautobot/dcim/tests/test_graphql.py +8 -3
  243. nautobot/dcim/tests/test_migrations.py +6 -11
  244. nautobot/dcim/tests/test_models.py +208 -158
  245. nautobot/dcim/tests/test_natural_ordering.py +12 -14
  246. nautobot/dcim/tests/test_signals.py +7 -4
  247. nautobot/dcim/tests/test_views.py +270 -264
  248. nautobot/dcim/urls.py +21 -26
  249. nautobot/dcim/views.py +14 -156
  250. nautobot/docs/additional-features/caching.md +6 -87
  251. nautobot/docs/additional-features/job-scheduling-and-approvals.md +3 -0
  252. nautobot/docs/additional-features/jobs.md +179 -197
  253. nautobot/docs/administration/nautobot-server.md +9 -24
  254. nautobot/docs/administration/nautobot-shell.md +6 -6
  255. nautobot/docs/administration/replicating-nautobot.md +0 -10
  256. nautobot/docs/configuration/index.md +9 -9
  257. nautobot/docs/configuration/optional-settings.md +32 -61
  258. nautobot/docs/configuration/required-settings.md +11 -52
  259. nautobot/docs/development/application-registry.md +2 -13
  260. nautobot/docs/development/best-practices.md +2 -1
  261. nautobot/docs/development/docker-compose-advanced-use-cases.md +1 -1
  262. nautobot/docs/development/extending-models.md +15 -17
  263. nautobot/docs/development/generic-views.md +0 -2
  264. nautobot/docs/development/getting-started.md +56 -6
  265. nautobot/docs/development/navigation-menu.md +22 -93
  266. nautobot/docs/development/react-ui.md +105 -0
  267. nautobot/docs/development/release-checklist.md +3 -3
  268. nautobot/docs/development/role-internals.md +1 -3
  269. nautobot/docs/development/style-guide.md +6 -4
  270. nautobot/docs/development/templates.md +2 -1
  271. nautobot/docs/docker/index.md +16 -14
  272. nautobot/docs/index.md +7 -3
  273. nautobot/docs/installation/index.md +4 -1
  274. nautobot/docs/installation/migrating-from-netbox.md +12 -43
  275. nautobot/docs/installation/migrating-from-postgresql.md +1 -1
  276. nautobot/docs/installation/nautobot.md +1 -1
  277. nautobot/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  278. nautobot/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  279. nautobot/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  280. nautobot/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  281. nautobot/docs/installation/tables/v2-code-removals.yaml +67 -0
  282. nautobot/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  283. nautobot/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  284. nautobot/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  285. nautobot/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  286. nautobot/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  287. nautobot/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  288. nautobot/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  289. nautobot/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  290. nautobot/docs/installation/upgrading-from-nautobot-v1.md +190 -636
  291. nautobot/docs/installation/upgrading.md +5 -2
  292. nautobot/docs/models/dcim/device.md +3 -0
  293. nautobot/docs/models/dcim/deviceredundancygroup.md +3 -3
  294. nautobot/docs/models/extras/computedfield.md +4 -4
  295. nautobot/docs/models/extras/dynamicgroup.md +9 -9
  296. nautobot/docs/models/extras/gitrepository.md +3 -0
  297. nautobot/docs/models/extras/job.md +1 -0
  298. nautobot/docs/models/extras/jobbutton.md +18 -13
  299. nautobot/docs/models/extras/jobhook.md +7 -4
  300. nautobot/docs/models/extras/jobresult.md +6 -2
  301. nautobot/docs/models/extras/relationship.md +2 -2
  302. nautobot/docs/models/extras/status.md +6 -19
  303. nautobot/docs/models/ipam/ipaddress.md +3 -0
  304. nautobot/docs/models/ipam/vrf.md +0 -3
  305. nautobot/docs/models/virtualization/virtualmachine.md +3 -0
  306. nautobot/docs/plugins/development.md +92 -24
  307. nautobot/docs/release-notes/version-1.5.md +96 -0
  308. nautobot/docs/release-notes/version-2.0.md +216 -0
  309. nautobot/docs/requirements.txt +5 -4
  310. nautobot/docs/rest-api/overview.md +384 -215
  311. nautobot/docs/rest-api/ui-related-endpoints.md +9 -0
  312. nautobot/extras/admin.py +3 -5
  313. nautobot/extras/api/customfields.py +15 -39
  314. nautobot/extras/api/fields.py +0 -11
  315. nautobot/extras/api/mixins.py +45 -0
  316. nautobot/extras/api/relationships.py +63 -159
  317. nautobot/extras/api/serializers.py +165 -706
  318. nautobot/extras/api/urls.py +1 -1
  319. nautobot/extras/api/views.py +295 -282
  320. nautobot/extras/apps.py +4 -7
  321. nautobot/extras/choices.py +11 -22
  322. nautobot/extras/constants.py +9 -3
  323. nautobot/extras/datasources/__init__.py +2 -0
  324. nautobot/extras/datasources/git.py +135 -186
  325. nautobot/extras/datasources/registry.py +25 -35
  326. nautobot/extras/factory.py +1 -3
  327. nautobot/extras/filters/__init__.py +49 -47
  328. nautobot/extras/filters/mixins.py +10 -8
  329. nautobot/extras/forms/forms.py +72 -148
  330. nautobot/extras/forms/mixins.py +34 -57
  331. nautobot/extras/health_checks.py +0 -33
  332. nautobot/extras/jobs.py +387 -566
  333. nautobot/extras/management/__init__.py +55 -48
  334. nautobot/extras/management/commands/renaturalize.py +0 -1
  335. nautobot/extras/management/commands/runjob.py +24 -62
  336. nautobot/extras/management/commands/webhook_receiver.py +0 -1
  337. nautobot/extras/managers.py +30 -7
  338. nautobot/extras/migrations/0001_initial_part_1.py +0 -1
  339. nautobot/extras/migrations/0002_initial_part_2.py +0 -1
  340. nautobot/extras/migrations/0003_initial_part_3.py +0 -1
  341. nautobot/extras/migrations/0004_populate_default_status_records.py +0 -1
  342. nautobot/extras/migrations/0005_configcontext_device_types.py +0 -1
  343. nautobot/extras/migrations/0006_graphqlquery.py +0 -1
  344. nautobot/extras/migrations/0007_configcontextschema.py +0 -1
  345. nautobot/extras/migrations/0008_jobresult__custom_field_data.py +0 -1
  346. nautobot/extras/migrations/0009_computedfield.py +0 -1
  347. nautobot/extras/migrations/0010_change_cf_validation_max_min_field_to_bigint.py +0 -1
  348. nautobot/extras/migrations/0011_fileattachment_fileproxy.py +0 -1
  349. nautobot/extras/migrations/0012_healthchecktestmodel.py +0 -1
  350. nautobot/extras/migrations/0013_default_fallback_value_computedfield.py +0 -1
  351. nautobot/extras/migrations/0014_auto_slug.py +0 -1
  352. nautobot/extras/migrations/0015_scheduled_job.py +0 -1
  353. nautobot/extras/migrations/0016_secret.py +0 -1
  354. nautobot/extras/migrations/0017_joblogentry.py +0 -1
  355. nautobot/extras/migrations/0018_joblog_data_migration.py +0 -2
  356. nautobot/extras/migrations/0019_joblogentry__meta_options__related_name.py +0 -1
  357. nautobot/extras/migrations/0020_customfield_changelog.py +0 -1
  358. nautobot/extras/migrations/0021_customfield_changelog_data.py +0 -1
  359. nautobot/extras/migrations/0022_objectchange_object_datav2.py +0 -1
  360. nautobot/extras/migrations/0023_job_model.py +0 -1
  361. nautobot/extras/migrations/0024_job_data_migration.py +0 -1
  362. nautobot/extras/migrations/0025_add_advanced_ui_boolean_to_customfield_conputedfield_and_relationship.py +0 -1
  363. nautobot/extras/migrations/0026_job_add_gitrepository_fk.py +0 -1
  364. nautobot/extras/migrations/0027_job_gitrepository_data_migration.py +0 -1
  365. nautobot/extras/migrations/0028_job_reduce_source.py +0 -1
  366. nautobot/extras/migrations/0029_dynamicgroup.py +0 -1
  367. nautobot/extras/migrations/0030_webhook_alter_unique_together.py +0 -1
  368. nautobot/extras/migrations/0031_tag_content_types.py +0 -1
  369. nautobot/extras/migrations/0032_tag_content_types_data_migration.py +0 -1
  370. nautobot/extras/migrations/0033_add__optimized_indexing.py +0 -1
  371. nautobot/extras/migrations/0034_alter_fileattachment_mimetype.py +0 -1
  372. nautobot/extras/migrations/0035_scheduledjob_crontab.py +0 -1
  373. nautobot/extras/migrations/0036_job_add_has_sensitive_variables.py +0 -1
  374. nautobot/extras/migrations/0037_configcontextschema__remove_name_unique__create_constraint_unique_name_owner.py +0 -1
  375. nautobot/extras/migrations/0038_configcontext_locations.py +0 -1
  376. nautobot/extras/migrations/0039_objectchange__add_change_context.py +0 -1
  377. nautobot/extras/migrations/0040_dynamicgroup__dynamicgroupmembership.py +0 -1
  378. nautobot/extras/migrations/0041_jobresult_job_kwargs.py +0 -1
  379. nautobot/extras/migrations/0042_job__add_is_job_hook_receiver.py +0 -1
  380. nautobot/extras/migrations/0043_note.py +0 -1
  381. nautobot/extras/migrations/0044_add_job_hook.py +0 -1
  382. nautobot/extras/migrations/0045_add_custom_field_slug.py +0 -1
  383. nautobot/extras/migrations/0046_populate_custom_field_slug_label.py +0 -1
  384. nautobot/extras/migrations/0047_enforce_custom_field_slug.py +0 -1
  385. nautobot/extras/migrations/0048_alter_objectchange_change_context_detail.py +0 -1
  386. nautobot/extras/migrations/0049_alter_tag_slug.py +0 -1
  387. nautobot/extras/migrations/0050_customfield_grouping.py +0 -1
  388. nautobot/extras/migrations/0051_add_job_task_queues.py +0 -1
  389. nautobot/extras/migrations/0052_configcontext_device_redundancy_groups.py +0 -1
  390. nautobot/extras/migrations/0053_relationship_required_on.py +0 -1
  391. nautobot/extras/migrations/0054_scheduledjob_kwargs_request_user_change.py +0 -1
  392. nautobot/extras/migrations/0055_configcontext_dynamic_groups.py +0 -1
  393. nautobot/extras/migrations/0056_objectchange_add_reverse_time_idx.py +0 -1
  394. nautobot/extras/migrations/0057_jobbutton.py +0 -1
  395. nautobot/extras/migrations/0058_jobresult_add_time_status_idxs.py +38 -0
  396. nautobot/extras/migrations/{0058_joblogentry_scheduledjob_webhook_data_migration.py → 0059_joblogentry_scheduledjob_webhook_data_migration.py} +1 -2
  397. nautobot/extras/migrations/{0059_alter_joblogentry_scheduledjob_webhook_fields.py → 0060_alter_joblogentry_scheduledjob_webhook_fields.py} +1 -2
  398. nautobot/extras/migrations/{0060_role_and_alter_status.py → 0061_role_and_alter_status.py} +1 -8
  399. nautobot/extras/migrations/{0061_collect_roles_from_related_apps_roles.py → 0062_collect_roles_from_related_apps_roles.py} +33 -33
  400. nautobot/extras/migrations/{0062_alter_role_options.py → 0063_alter_role_options.py} +1 -2
  401. nautobot/extras/migrations/{0063_alter_configcontext_and_add_new_role.py → 0064_alter_configcontext_and_add_new_role.py} +1 -2
  402. nautobot/extras/migrations/0065_configcontext_data_migrations.py +44 -0
  403. nautobot/extras/migrations/{0065_rename_configcontext_role.py → 0066_rename_configcontext_role.py} +1 -2
  404. nautobot/extras/migrations/{0066_jobresult__add_celery_fields.py → 0067_jobresult__add_celery_fields.py} +36 -3
  405. nautobot/extras/migrations/{0067_created_datetime.py → 0068_created_datetime.py} +1 -2
  406. nautobot/extras/migrations/{0068_remove_site_and_region_attributes_from_config_context.py → 0069_remove_site_and_region_attributes_from_config_context.py} +1 -2
  407. nautobot/extras/migrations/{0069_replace_related_names.py → 0070_replace_related_names.py} +1 -1
  408. nautobot/extras/migrations/{0070_rename_model_fields.py → 0071_rename_model_fields.py} +1 -2
  409. nautobot/extras/migrations/0072_job__unique_name_data_migration.py +86 -0
  410. nautobot/extras/migrations/{0072_job__unique_name.py → 0073_job__unique_name.py} +13 -10
  411. nautobot/extras/migrations/{0073_remove_gitrepository_fields.py → 0074_remove_gitrepository_fields.py} +1 -2
  412. nautobot/extras/migrations/{0074_rename_slug_to_key_for_custom_field.py → 0075_rename_slug_to_key_for_custom_field.py} +1 -1
  413. nautobot/extras/migrations/{0075_migrate_custom_field_data.py → 0076_migrate_custom_field_data.py} +1 -1
  414. 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
  415. nautobot/extras/migrations/0078_remove_slug.py +45 -0
  416. nautobot/extras/migrations/0079_tagsfield.py +28 -0
  417. nautobot/extras/migrations/0080_rename_relationship_slug_to_key.py +17 -0
  418. nautobot/extras/migrations/0081_rename_relationship_name_to_label.py +29 -0
  419. nautobot/extras/migrations/0082_ensure_relationship_keys_are_unique.py +43 -0
  420. nautobot/extras/migrations/0083_rename_computed_field_slug_to_key.py +21 -0
  421. nautobot/extras/migrations/0084_taggeditem_cleanup.py +43 -0
  422. nautobot/extras/migrations/0085_taggeditem_uniqueness.py +22 -0
  423. nautobot/extras/migrations/0086_job__celery_task_fields__dryrun_support.py +81 -0
  424. nautobot/extras/migrations/0087_job__commit_default_data_migration.py +26 -0
  425. nautobot/extras/migrations/0088_joblogentry__log_level_default.py +17 -0
  426. nautobot/extras/migrations/0089_joblogentry__log_level_data_migration.py +34 -0
  427. nautobot/extras/migrations/0090_scheduledjob__data_migration.py +57 -0
  428. nautobot/extras/models/__init__.py +2 -3
  429. nautobot/extras/models/change_logging.py +0 -36
  430. nautobot/extras/models/customfields.py +39 -33
  431. nautobot/extras/models/datasources.py +48 -50
  432. nautobot/extras/models/groups.py +5 -12
  433. nautobot/extras/models/jobs.py +190 -323
  434. nautobot/extras/models/mixins.py +0 -71
  435. nautobot/extras/models/models.py +1 -22
  436. nautobot/extras/models/relationships.py +20 -21
  437. nautobot/extras/models/roles.py +0 -23
  438. nautobot/extras/models/secrets.py +2 -31
  439. nautobot/extras/models/statuses.py +6 -5
  440. nautobot/extras/models/tags.py +2 -17
  441. nautobot/extras/navigation.py +89 -307
  442. nautobot/extras/plugins/__init__.py +3 -121
  443. nautobot/extras/plugins/utils.py +0 -3
  444. nautobot/extras/plugins/validators.py +5 -4
  445. nautobot/extras/plugins/views.py +16 -4
  446. nautobot/extras/querysets.py +1 -7
  447. nautobot/extras/registry.py +3 -0
  448. nautobot/extras/signals.py +26 -60
  449. nautobot/extras/tables.py +42 -49
  450. nautobot/extras/tasks.py +0 -12
  451. nautobot/extras/templates/extras/configcontext.html +1 -1
  452. nautobot/extras/templates/extras/configcontextschema.html +16 -1
  453. nautobot/extras/templates/extras/customfield.html +0 -13
  454. nautobot/extras/templates/extras/dynamicgroup_edit.html +0 -1
  455. nautobot/extras/templates/extras/gitrepository.html +3 -3
  456. nautobot/extras/templates/extras/inc/jobresult.html +10 -0
  457. nautobot/extras/templates/extras/inc/panel_jobhistory.html +1 -1
  458. nautobot/extras/templates/extras/job.html +35 -25
  459. nautobot/extras/templates/extras/job_approval_request.html +15 -30
  460. nautobot/extras/templates/extras/job_detail.html +13 -31
  461. nautobot/extras/templates/extras/job_edit.html +14 -17
  462. nautobot/extras/templates/extras/jobresult.html +24 -6
  463. nautobot/extras/templates/extras/objectchange_list.html +1 -1
  464. nautobot/extras/templates/extras/scheduledjob.html +2 -2
  465. nautobot/extras/templates/extras/secret.html +28 -0
  466. nautobot/extras/templates/extras/secret_edit.html +0 -1
  467. nautobot/extras/templates/extras/secretsgroup_edit.html +0 -1
  468. nautobot/extras/templatetags/custom_links.py +0 -2
  469. nautobot/extras/templatetags/job_buttons.py +1 -0
  470. nautobot/extras/templatetags/plugins.py +0 -1
  471. nautobot/extras/{tests/example_jobs → test_jobs}/api_test_job.py +13 -6
  472. nautobot/extras/test_jobs/atomic_transaction.py +53 -0
  473. nautobot/extras/test_jobs/dry_run.py +29 -0
  474. nautobot/extras/{tests/example_jobs/test_duplicate_name.py → test_jobs/duplicate_name.py} +4 -0
  475. nautobot/extras/test_jobs/duplicate_name2.py +9 -0
  476. nautobot/extras/test_jobs/fail.py +23 -0
  477. nautobot/extras/{tests/example_jobs/test_field_default.py → test_jobs/field_default.py} +4 -0
  478. nautobot/extras/{tests/example_jobs/test_field_order.py → test_jobs/field_order.py} +4 -0
  479. nautobot/extras/{tests/example_jobs/test_file_upload_fail.py → test_jobs/file_upload_fail.py} +11 -6
  480. nautobot/extras/test_jobs/file_upload_pass.py +25 -0
  481. nautobot/extras/test_jobs/has_sensitive_variables.py +25 -0
  482. nautobot/extras/test_jobs/ipaddress_vars.py +66 -0
  483. nautobot/extras/test_jobs/job_button_receiver.py +28 -0
  484. nautobot/extras/test_jobs/job_hook_receiver.py +29 -0
  485. nautobot/extras/test_jobs/job_variables.py +88 -0
  486. nautobot/extras/test_jobs/location_with_custom_field.py +45 -0
  487. nautobot/extras/test_jobs/log_redaction.py +20 -0
  488. nautobot/extras/test_jobs/log_skip_db_logging.py +17 -0
  489. nautobot/extras/test_jobs/modify_db.py +25 -0
  490. nautobot/extras/{tests/example_jobs/test_no_field_order.py → test_jobs/no_field_order.py} +4 -0
  491. nautobot/extras/test_jobs/object_var_optional.py +21 -0
  492. nautobot/extras/test_jobs/object_var_required.py +21 -0
  493. nautobot/extras/test_jobs/object_vars.py +26 -0
  494. nautobot/extras/test_jobs/pass.py +25 -0
  495. nautobot/extras/test_jobs/profiling.py +32 -0
  496. nautobot/extras/test_jobs/read_only_job.py +15 -0
  497. nautobot/extras/{tests/example_jobs/test_required_args.py → test_jobs/required_args.py} +4 -0
  498. 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
  499. nautobot/extras/{tests/example_jobs/test_task_queues.py → test_jobs/task_queues.py} +5 -1
  500. nautobot/extras/tests/integration/__init__.py +3 -3
  501. nautobot/extras/tests/integration/test_computedfields.py +1 -1
  502. nautobot/extras/tests/integration/test_configcontextschema.py +7 -5
  503. nautobot/extras/tests/integration/test_customfields.py +4 -2
  504. nautobot/extras/tests/integration/test_dynamicgroups.py +2 -2
  505. nautobot/extras/tests/integration/test_jobs.py +25 -27
  506. nautobot/extras/tests/integration/test_notes.py +8 -4
  507. nautobot/extras/tests/integration/test_plugins.py +4 -4
  508. nautobot/extras/tests/integration/test_relationships.py +2 -2
  509. nautobot/extras/tests/test_api.py +371 -381
  510. nautobot/extras/tests/test_changelog.py +17 -16
  511. nautobot/extras/tests/test_context_managers.py +5 -6
  512. nautobot/extras/tests/test_customfields.py +112 -73
  513. nautobot/extras/tests/test_datasources.py +191 -117
  514. nautobot/extras/tests/test_dynamicgroups.py +45 -68
  515. nautobot/extras/tests/test_filters.py +170 -130
  516. nautobot/extras/tests/test_forms.py +107 -109
  517. nautobot/extras/tests/{test_scripts.py → test_job_variables.py} +43 -49
  518. nautobot/extras/tests/test_jobs.py +271 -273
  519. nautobot/extras/tests/test_management.py +3 -6
  520. nautobot/extras/tests/test_migrations.py +5 -3
  521. nautobot/extras/tests/test_models.py +121 -173
  522. nautobot/extras/tests/test_notes.py +0 -1
  523. nautobot/extras/tests/test_plugins.py +55 -89
  524. nautobot/extras/tests/test_relationships.py +174 -130
  525. nautobot/extras/tests/test_tags.py +6 -12
  526. nautobot/extras/tests/test_utils.py +31 -1
  527. nautobot/extras/tests/test_views.py +223 -184
  528. nautobot/extras/tests/test_webhooks.py +16 -15
  529. nautobot/extras/urls.py +69 -69
  530. nautobot/extras/utils.py +137 -163
  531. nautobot/extras/views.py +81 -153
  532. nautobot/ipam/api/fields.py +17 -0
  533. nautobot/ipam/api/serializers.py +77 -164
  534. nautobot/ipam/api/urls.py +4 -1
  535. nautobot/ipam/api/views.py +28 -19
  536. nautobot/ipam/apps.py +1 -0
  537. nautobot/ipam/choices.py +5 -12
  538. nautobot/ipam/constants.py +1 -0
  539. nautobot/ipam/factory.py +41 -30
  540. nautobot/ipam/filters.py +58 -25
  541. nautobot/ipam/forms.py +82 -211
  542. nautobot/ipam/graphql/types.py +0 -9
  543. nautobot/ipam/lookups.py +13 -8
  544. nautobot/ipam/management/commands/__init__.py +0 -0
  545. nautobot/ipam/management/commands/fix_prefix_broadcast.py +17 -0
  546. nautobot/ipam/migrations/0001_initial_part_1.py +0 -1
  547. nautobot/ipam/migrations/0002_initial_part_2.py +0 -1
  548. nautobot/ipam/migrations/0003_remove_max_length.py +0 -1
  549. nautobot/ipam/migrations/0004_fixup_p2p_broadcast.py +0 -1
  550. nautobot/ipam/migrations/0005_auto_slug.py +0 -1
  551. nautobot/ipam/migrations/0006_ipaddress_nat_outside_list.py +0 -1
  552. nautobot/ipam/migrations/0007_add_natural_indexing.py +0 -1
  553. nautobot/ipam/migrations/0008_prefix_vlan_vlangroup_location.py +0 -1
  554. nautobot/ipam/migrations/0009_alter_vlan_name.py +0 -1
  555. nautobot/ipam/migrations/0010_alter_ipam_role_add_new_role.py +1 -2
  556. nautobot/ipam/migrations/0011_migrate_ipam_role_data.py +32 -39
  557. nautobot/ipam/migrations/0012_rename_ipam_roles.py +0 -1
  558. nautobot/ipam/migrations/0013_delete_role.py +0 -1
  559. nautobot/ipam/migrations/0014_rename_foreign_keys_and_related_names.py +0 -1
  560. nautobot/ipam/migrations/0015_prefix_add_type.py +0 -1
  561. nautobot/ipam/migrations/0016_prefix_type_data_migration.py +0 -3
  562. nautobot/ipam/migrations/0017_prefix_remove_is_pool.py +0 -1
  563. nautobot/ipam/migrations/0018_remove_site_foreign_key_from_ipam_models.py +0 -1
  564. nautobot/ipam/migrations/0019_created_datetime.py +0 -1
  565. nautobot/ipam/migrations/0020_related_name_changes.py +1 -2
  566. nautobot/ipam/migrations/0021_prefix_add_rir_and_date_allocated.py +0 -1
  567. nautobot/ipam/migrations/0022_aggregate_to_prefix_data_migration.py +3 -5
  568. nautobot/ipam/migrations/0023_delete_aggregate.py +0 -1
  569. nautobot/ipam/migrations/0024_interface_to_ipaddress_m2m.py +0 -1
  570. nautobot/ipam/migrations/0025_interface_ipaddress_m2m_data_migration.py +0 -1
  571. nautobot/ipam/migrations/0026_ipaddress_remove_assigned_object.py +0 -1
  572. nautobot/ipam/migrations/0027_remove_rir_slug.py +16 -0
  573. nautobot/ipam/migrations/0028_tagsfield.py +44 -0
  574. nautobot/ipam/migrations/0029_ip_address_to_interface_uniqueness_constraints.py +18 -0
  575. nautobot/ipam/migrations/0030_ipam__namespaces.py +231 -0
  576. nautobot/ipam/migrations/0031_ipam__prefix__add_parent.py +58 -0
  577. nautobot/ipam/migrations/0032_ipam__namespaces_finish.py +63 -0
  578. nautobot/ipam/migrations/0033_fixup_null_statuses.py +26 -0
  579. nautobot/ipam/migrations/0034_status_nonnullable.py +36 -0
  580. nautobot/ipam/models.py +579 -368
  581. nautobot/ipam/navigation.py +36 -159
  582. nautobot/ipam/querysets.py +117 -90
  583. nautobot/ipam/signals.py +89 -0
  584. nautobot/ipam/tables.py +86 -28
  585. nautobot/ipam/templates/ipam/ipaddress.html +14 -30
  586. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -0
  587. nautobot/ipam/templates/ipam/namespace_ipaddresses.html +11 -0
  588. nautobot/ipam/templates/ipam/namespace_prefixes.html +11 -0
  589. nautobot/ipam/templates/ipam/namespace_retrieve.html +42 -0
  590. nautobot/ipam/templates/ipam/namespace_vrfs.html +11 -0
  591. nautobot/ipam/templates/ipam/prefix.html +27 -33
  592. nautobot/ipam/templates/ipam/prefix_edit.html +7 -1
  593. nautobot/ipam/templates/ipam/vlangroup.html +0 -13
  594. nautobot/ipam/templates/ipam/vrf.html +6 -4
  595. nautobot/ipam/templates/ipam/vrf_edit.html +20 -2
  596. nautobot/ipam/tests/integration/test_prefixes.py +4 -27
  597. nautobot/ipam/tests/test_api.py +60 -61
  598. nautobot/ipam/tests/test_filters.py +187 -126
  599. nautobot/ipam/tests/test_forms.py +12 -6
  600. nautobot/ipam/tests/test_graphql.py +8 -6
  601. nautobot/ipam/tests/test_migrations.py +8 -13
  602. nautobot/ipam/tests/test_models.py +426 -274
  603. nautobot/ipam/tests/test_ordering.py +6 -3
  604. nautobot/ipam/tests/test_querysets.py +340 -96
  605. nautobot/ipam/tests/test_views.py +100 -55
  606. nautobot/ipam/urls.py +28 -5
  607. nautobot/ipam/{utils.py → utils/__init__.py} +2 -2
  608. nautobot/ipam/utils/migrations.py +713 -0
  609. nautobot/ipam/views.py +237 -122
  610. nautobot/project-static/docs/404.html +1399 -166
  611. nautobot/project-static/docs/additional-features/caching.html +1416 -320
  612. nautobot/project-static/docs/additional-features/change-logging.html +1389 -190
  613. nautobot/project-static/docs/additional-features/config-contexts.html +1389 -190
  614. nautobot/project-static/docs/additional-features/graphql.html +1389 -190
  615. nautobot/project-static/docs/additional-features/healthcheck.html +1389 -190
  616. nautobot/project-static/docs/additional-features/job-scheduling-and-approvals.html +1393 -190
  617. nautobot/project-static/docs/additional-features/jobs.html +1677 -460
  618. nautobot/project-static/docs/additional-features/napalm.html +1389 -190
  619. nautobot/project-static/docs/additional-features/prometheus-metrics.html +1389 -190
  620. nautobot/project-static/docs/additional-features/template-filters.html +1389 -190
  621. nautobot/project-static/docs/administration/celery-queues.html +1389 -190
  622. nautobot/project-static/docs/administration/nautobot-server.html +1553 -375
  623. nautobot/project-static/docs/administration/nautobot-shell.html +1395 -196
  624. nautobot/project-static/docs/administration/permissions.html +1389 -190
  625. nautobot/project-static/docs/administration/replicating-nautobot.html +1387 -207
  626. nautobot/project-static/docs/apps/index.html +1389 -190
  627. nautobot/project-static/docs/apps/nautobot-apps.html +1387 -175
  628. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js +29 -0
  629. nautobot/project-static/docs/assets/javascripts/bundle.51198bba.min.js.map +8 -0
  630. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js → search.208ed371.min.js} +9 -15
  631. nautobot/project-static/docs/assets/javascripts/workers/{search.16e2a7d4.min.js.map → search.208ed371.min.js.map} +4 -4
  632. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css +1 -0
  633. nautobot/project-static/docs/assets/stylesheets/main.ded33207.min.css.map +1 -0
  634. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css +1 -0
  635. nautobot/project-static/docs/assets/stylesheets/palette.a0c5b2b5.min.css.map +1 -0
  636. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +1775 -590
  637. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +1389 -190
  638. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3588 -1922
  639. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +1461 -262
  640. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +1401 -170
  641. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +1396 -191
  642. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +2095 -894
  643. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +2357 -1194
  644. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2258 -940
  645. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +1389 -190
  646. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1400 -201
  647. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +11068 -7861
  648. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +2867 -2224
  649. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +1389 -190
  650. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +2641 -1573
  651. nautobot/project-static/docs/configuration/authentication/ldap.html +1389 -190
  652. nautobot/project-static/docs/configuration/authentication/remote.html +1389 -190
  653. nautobot/project-static/docs/configuration/authentication/sso.html +1389 -190
  654. nautobot/project-static/docs/configuration/index.html +1398 -199
  655. nautobot/project-static/docs/configuration/optional-settings.html +1418 -274
  656. nautobot/project-static/docs/configuration/required-settings.html +1419 -287
  657. nautobot/project-static/docs/core-functionality/circuits.html +1446 -247
  658. nautobot/project-static/docs/core-functionality/device-types.html +1448 -249
  659. nautobot/project-static/docs/core-functionality/devices.html +1452 -249
  660. nautobot/project-static/docs/core-functionality/ipam.html +1452 -253
  661. nautobot/project-static/docs/core-functionality/power.html +1448 -249
  662. nautobot/project-static/docs/core-functionality/secrets.html +1448 -249
  663. nautobot/project-static/docs/core-functionality/services.html +1448 -249
  664. nautobot/project-static/docs/core-functionality/sites-and-racks.html +1448 -249
  665. nautobot/project-static/docs/core-functionality/tenancy.html +1448 -249
  666. nautobot/project-static/docs/core-functionality/virtualization.html +1452 -249
  667. nautobot/project-static/docs/core-functionality/vlans.html +1448 -249
  668. nautobot/project-static/docs/development/application-registry.html +1393 -214
  669. nautobot/project-static/docs/development/best-practices.html +1392 -192
  670. nautobot/project-static/docs/development/docker-compose-advanced-use-cases.html +1390 -191
  671. nautobot/project-static/docs/development/extending-models.html +1443 -257
  672. nautobot/project-static/docs/development/generic-views.html +1403 -174
  673. nautobot/project-static/docs/development/getting-started.html +1568 -262
  674. nautobot/project-static/docs/development/homepage.html +1389 -190
  675. nautobot/project-static/docs/development/index.html +1389 -190
  676. nautobot/project-static/docs/development/model-features.html +1389 -190
  677. nautobot/project-static/docs/development/natural-keys.html +1389 -190
  678. nautobot/project-static/docs/development/navigation-menu.html +1451 -330
  679. nautobot/project-static/docs/development/react-ui.html +4199 -0
  680. nautobot/project-static/docs/development/release-checklist.html +1392 -193
  681. nautobot/project-static/docs/development/role-internals.html +1402 -172
  682. nautobot/project-static/docs/development/style-guide.html +1399 -199
  683. nautobot/project-static/docs/development/templates.html +1391 -191
  684. nautobot/project-static/docs/development/testing.html +1389 -190
  685. nautobot/project-static/docs/development/user-preferences.html +1389 -190
  686. nautobot/project-static/docs/docker/index.html +1408 -206
  687. nautobot/project-static/docs/index.html +1397 -180
  688. nautobot/project-static/docs/installation/centos.html +1401 -170
  689. nautobot/project-static/docs/installation/external-authentication.html +1389 -190
  690. nautobot/project-static/docs/installation/http-server.html +1389 -190
  691. nautobot/project-static/docs/installation/index.html +1394 -191
  692. nautobot/project-static/docs/installation/migrating-from-netbox.html +1452 -305
  693. nautobot/project-static/docs/installation/migrating-from-postgresql.html +1390 -191
  694. nautobot/project-static/docs/installation/nautobot.html +1390 -191
  695. nautobot/project-static/docs/installation/region-and-site-data-migration-guide.html +1389 -190
  696. nautobot/project-static/docs/installation/selinux-troubleshooting.html +1401 -170
  697. nautobot/project-static/docs/installation/services.html +1389 -190
  698. nautobot/project-static/docs/installation/tables/v2-api-behavior-changes.yaml +70 -0
  699. nautobot/project-static/docs/installation/tables/v2-api-removed-fields.yaml +142 -0
  700. nautobot/project-static/docs/installation/tables/v2-api-renamed-fields.yaml +124 -0
  701. nautobot/project-static/docs/installation/tables/v2-code-location-changes.yaml +241 -0
  702. nautobot/project-static/docs/installation/tables/v2-code-removals.yaml +67 -0
  703. nautobot/project-static/docs/installation/tables/v2-database-behavior-changes.yaml +37 -0
  704. nautobot/project-static/docs/installation/tables/v2-database-removed-fields.yaml +166 -0
  705. nautobot/project-static/docs/installation/tables/v2-database-renamed-fields.yaml +340 -0
  706. nautobot/project-static/docs/installation/tables/v2-filters-corrected-fields.yaml +28 -0
  707. nautobot/project-static/docs/installation/tables/v2-filters-enhanced-fields.yaml +241 -0
  708. nautobot/project-static/docs/installation/tables/v2-filters-removed-fields.yaml +553 -0
  709. nautobot/project-static/docs/installation/tables/v2-filters-renamed-fields.yaml +223 -0
  710. nautobot/project-static/docs/installation/tables/v2-logging-renamed-loggers.yaml +23 -0
  711. nautobot/project-static/docs/installation/ubuntu.html +1401 -170
  712. nautobot/project-static/docs/installation/upgrading-from-nautobot-v1.html +4254 -1923
  713. nautobot/project-static/docs/installation/upgrading.html +1395 -192
  714. nautobot/project-static/docs/models/circuits/circuit.html +1427 -174
  715. nautobot/project-static/docs/models/circuits/circuittermination.html +1427 -174
  716. nautobot/project-static/docs/models/circuits/circuittype.html +1427 -174
  717. nautobot/project-static/docs/models/circuits/provider.html +1427 -174
  718. nautobot/project-static/docs/models/circuits/providernetwork.html +1427 -174
  719. nautobot/project-static/docs/models/dcim/cable.html +1458 -174
  720. nautobot/project-static/docs/models/dcim/consoleport.html +1427 -174
  721. nautobot/project-static/docs/models/dcim/consoleporttemplate.html +1427 -174
  722. nautobot/project-static/docs/models/dcim/consoleserverport.html +1427 -174
  723. nautobot/project-static/docs/models/dcim/consoleserverporttemplate.html +1427 -174
  724. nautobot/project-static/docs/models/dcim/device.html +1431 -174
  725. nautobot/project-static/docs/models/dcim/devicebay.html +1427 -174
  726. nautobot/project-static/docs/models/dcim/devicebaytemplate.html +1427 -174
  727. nautobot/project-static/docs/models/dcim/deviceredundancygroup.html +1522 -177
  728. nautobot/project-static/docs/models/dcim/devicetype.html +1427 -174
  729. nautobot/project-static/docs/models/dcim/frontport.html +1427 -174
  730. nautobot/project-static/docs/models/dcim/frontporttemplate.html +1427 -174
  731. nautobot/project-static/docs/models/dcim/interface.html +1427 -174
  732. nautobot/project-static/docs/models/dcim/interfacetemplate.html +1427 -174
  733. nautobot/project-static/docs/models/dcim/inventoryitem.html +1427 -174
  734. nautobot/project-static/docs/models/dcim/location.html +1427 -174
  735. nautobot/project-static/docs/models/dcim/locationtype.html +1427 -174
  736. nautobot/project-static/docs/models/dcim/manufacturer.html +1427 -174
  737. nautobot/project-static/docs/models/dcim/platform.html +1427 -174
  738. nautobot/project-static/docs/models/dcim/powerfeed.html +1425 -172
  739. nautobot/project-static/docs/models/dcim/poweroutlet.html +1427 -174
  740. nautobot/project-static/docs/models/dcim/poweroutlettemplate.html +1427 -174
  741. nautobot/project-static/docs/models/dcim/powerpanel.html +1425 -172
  742. nautobot/project-static/docs/models/dcim/powerport.html +1427 -174
  743. nautobot/project-static/docs/models/dcim/powerporttemplate.html +1427 -174
  744. nautobot/project-static/docs/models/dcim/rack.html +1427 -174
  745. nautobot/project-static/docs/models/dcim/rackgroup.html +1427 -174
  746. nautobot/project-static/docs/models/dcim/rackreservation.html +1427 -174
  747. nautobot/project-static/docs/models/dcim/rearport.html +1427 -174
  748. nautobot/project-static/docs/models/dcim/rearporttemplate.html +1427 -174
  749. nautobot/project-static/docs/models/dcim/region.html +1401 -170
  750. nautobot/project-static/docs/models/dcim/site.html +1401 -170
  751. nautobot/project-static/docs/models/dcim/virtualchassis.html +1425 -172
  752. nautobot/project-static/docs/models/extras/computedfield.html +1393 -194
  753. nautobot/project-static/docs/models/extras/configcontext.html +1465 -174
  754. nautobot/project-static/docs/models/extras/configcontextschema.html +1421 -168
  755. nautobot/project-static/docs/models/extras/customfield.html +1389 -190
  756. nautobot/project-static/docs/models/extras/customlink.html +1389 -190
  757. nautobot/project-static/docs/models/extras/dynamicgroup.html +1398 -199
  758. nautobot/project-static/docs/models/extras/exporttemplate.html +1389 -190
  759. nautobot/project-static/docs/models/extras/gitrepository.html +1393 -190
  760. nautobot/project-static/docs/models/extras/graphqlquery.html +1469 -171
  761. nautobot/project-static/docs/models/extras/imageattachment.html +1434 -181
  762. nautobot/project-static/docs/models/extras/job.html +1411 -157
  763. nautobot/project-static/docs/models/extras/jobbutton.html +1410 -207
  764. nautobot/project-static/docs/models/extras/jobhook.html +1397 -194
  765. nautobot/project-static/docs/models/extras/joblogentry.html +1408 -155
  766. nautobot/project-static/docs/models/extras/jobresult.html +1417 -159
  767. nautobot/project-static/docs/models/extras/note.html +1389 -190
  768. nautobot/project-static/docs/models/extras/relationship.html +1391 -192
  769. nautobot/project-static/docs/models/extras/role.html +1495 -198
  770. nautobot/project-static/docs/models/extras/secret.html +1492 -201
  771. nautobot/project-static/docs/models/extras/secretsgroup.html +1410 -157
  772. nautobot/project-static/docs/models/extras/status.html +1381 -221
  773. nautobot/project-static/docs/models/extras/tag.html +1389 -190
  774. nautobot/project-static/docs/models/extras/webhook.html +1389 -190
  775. nautobot/project-static/docs/models/ipam/ipaddress.html +1488 -200
  776. nautobot/project-static/docs/models/ipam/prefix.html +1410 -157
  777. nautobot/project-static/docs/models/ipam/rir.html +1410 -157
  778. nautobot/project-static/docs/models/ipam/routetarget.html +1410 -157
  779. nautobot/project-static/docs/models/ipam/service.html +1410 -157
  780. nautobot/project-static/docs/models/ipam/vlan.html +1410 -157
  781. nautobot/project-static/docs/models/ipam/vlangroup.html +1410 -157
  782. nautobot/project-static/docs/models/ipam/vrf.html +1410 -161
  783. nautobot/project-static/docs/models/tenancy/tenant.html +1412 -159
  784. nautobot/project-static/docs/models/tenancy/tenantgroup.html +1412 -159
  785. nautobot/project-static/docs/models/users/objectpermission.html +1462 -171
  786. nautobot/project-static/docs/models/users/token.html +1410 -157
  787. nautobot/project-static/docs/models/virtualization/cluster.html +1410 -157
  788. nautobot/project-static/docs/models/virtualization/clustergroup.html +1410 -157
  789. nautobot/project-static/docs/models/virtualization/clustertype.html +1410 -157
  790. nautobot/project-static/docs/models/virtualization/virtualmachine.html +1414 -157
  791. nautobot/project-static/docs/models/virtualization/vminterface.html +1410 -157
  792. nautobot/project-static/docs/objects.inv +0 -0
  793. nautobot/project-static/docs/plugins/development.html +1916 -646
  794. nautobot/project-static/docs/plugins/index.html +1389 -190
  795. nautobot/project-static/docs/plugins/porting-from-netbox.html +1389 -190
  796. nautobot/project-static/docs/release-notes/index.html +1389 -190
  797. nautobot/project-static/docs/release-notes/version-1.0.html +1389 -190
  798. nautobot/project-static/docs/release-notes/version-1.1.html +1389 -190
  799. nautobot/project-static/docs/release-notes/version-1.2.html +1389 -190
  800. nautobot/project-static/docs/release-notes/version-1.3.html +1389 -190
  801. nautobot/project-static/docs/release-notes/version-1.4.html +1389 -190
  802. nautobot/project-static/docs/release-notes/version-1.5.html +2016 -397
  803. nautobot/project-static/docs/release-notes/version-2.0.html +1935 -287
  804. nautobot/project-static/docs/requirements.txt +5 -4
  805. nautobot/project-static/docs/rest-api/authentication.html +1389 -190
  806. nautobot/project-static/docs/rest-api/filtering.html +1389 -190
  807. nautobot/project-static/docs/rest-api/overview.html +2002 -576
  808. nautobot/project-static/docs/rest-api/ui-related-endpoints.html +4057 -0
  809. nautobot/project-static/docs/search/search_index.json +1 -1
  810. nautobot/project-static/docs/sitemap.xml +197 -187
  811. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  812. nautobot/project-static/docs/user-guides/custom-fields.html +1390 -191
  813. nautobot/project-static/docs/user-guides/getting-started/creating-devices.html +1392 -193
  814. nautobot/project-static/docs/user-guides/getting-started/index.html +1388 -189
  815. nautobot/project-static/docs/user-guides/getting-started/interfaces.html +1388 -189
  816. nautobot/project-static/docs/user-guides/getting-started/ipam.html +1386 -187
  817. nautobot/project-static/docs/user-guides/getting-started/platforms.html +1448 -249
  818. nautobot/project-static/docs/user-guides/getting-started/regions.html +1411 -212
  819. nautobot/project-static/docs/user-guides/getting-started/search-bar.html +1395 -196
  820. nautobot/project-static/docs/user-guides/getting-started/tenants.html +1448 -249
  821. nautobot/project-static/docs/user-guides/getting-started/vlans-and-vlan-groups.html +1448 -249
  822. nautobot/project-static/docs/user-guides/git-data-source.html +1405 -206
  823. nautobot/project-static/docs/user-guides/graphql.html +1402 -203
  824. nautobot/project-static/docs/user-guides/relationships.html +1448 -249
  825. nautobot/project-static/docs/user-guides/s3-django-storage.html +1448 -249
  826. nautobot/project-static/js/forms.js +16 -9
  827. nautobot/project-static/js/theme.js +5 -0
  828. nautobot/tenancy/api/serializers.py +4 -34
  829. nautobot/tenancy/api/urls.py +1 -1
  830. nautobot/tenancy/filters/__init__.py +9 -7
  831. nautobot/tenancy/filters/mixins.py +3 -2
  832. nautobot/tenancy/forms.py +3 -36
  833. nautobot/tenancy/migrations/0001_initial.py +0 -1
  834. nautobot/tenancy/migrations/0002_auto_slug.py +0 -1
  835. nautobot/tenancy/migrations/0003_mptt_to_tree_queries.py +0 -1
  836. nautobot/tenancy/migrations/0004_change_tree_manager_on_tree_models.py +0 -1
  837. nautobot/tenancy/migrations/0005_rename_foreign_keys_and_related_names.py +0 -1
  838. nautobot/tenancy/migrations/0006_created_datetime.py +0 -1
  839. nautobot/tenancy/migrations/0007_remove_tenant_tenantgroup_slug.py +20 -0
  840. nautobot/tenancy/migrations/0008_tagsfield.py +19 -0
  841. nautobot/tenancy/models.py +0 -30
  842. nautobot/tenancy/navigation.py +6 -39
  843. nautobot/tenancy/tables.py +4 -4
  844. nautobot/tenancy/templates/tenancy/tenant.html +12 -12
  845. nautobot/tenancy/templates/tenancy/tenant_edit.html +0 -1
  846. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  847. nautobot/tenancy/tests/test_api.py +1 -12
  848. nautobot/tenancy/tests/test_filters.py +20 -12
  849. nautobot/tenancy/tests/test_views.py +11 -29
  850. nautobot/tenancy/urls.py +10 -10
  851. nautobot/tenancy/views.py +0 -3
  852. nautobot/ui/.eslintignore +6 -0
  853. nautobot/ui/.gitignore +10 -0
  854. nautobot/ui/.prettierignore +9 -0
  855. nautobot/ui/.prettierrc +4 -0
  856. nautobot/ui/README.md +33 -0
  857. nautobot/ui/app_imports.js.j2 +7 -0
  858. nautobot/ui/craco.config.js +46 -0
  859. nautobot/ui/jsconfig-base.json +11 -0
  860. nautobot/ui/jsconfig.json +5 -0
  861. nautobot/ui/lib/nautobot-craco-alias-plugin.js +40 -0
  862. nautobot/ui/package-lock.json +21451 -0
  863. nautobot/ui/package.json +70 -0
  864. nautobot/ui/public/index.html +47 -0
  865. nautobot/ui/public/logo192.png +0 -0
  866. nautobot/ui/public/logo512.png +0 -0
  867. nautobot/ui/public/manifest.json +25 -0
  868. nautobot/ui/public/nautobot_logo.svg +131 -0
  869. nautobot/ui/public/robots.txt +3 -0
  870. nautobot/ui/src/App.js +71 -0
  871. nautobot/ui/src/components/AppFullWidthComponents.js +8 -0
  872. nautobot/ui/src/components/AppTab.js +40 -0
  873. nautobot/ui/src/components/Apps.js +60 -0
  874. nautobot/ui/src/components/HomeChangelogPanel.js +98 -0
  875. nautobot/ui/src/components/HomePanel.js +58 -0
  876. nautobot/ui/src/components/JobHistoryTable.js +78 -0
  877. nautobot/ui/src/components/Layout.js +53 -0
  878. nautobot/ui/src/components/LoadingWidget.js +25 -0
  879. nautobot/ui/src/components/Navbar.js +116 -0
  880. nautobot/ui/src/components/NotificationPopover.js +27 -0
  881. nautobot/ui/src/components/ObjectListTable.js +209 -0
  882. nautobot/ui/src/components/ReferenceDataTag.js +35 -0
  883. nautobot/ui/src/components/RouterButton.js +10 -0
  884. nautobot/ui/src/components/RouterLink.js +10 -0
  885. nautobot/ui/src/components/SidebarNav.js +147 -0
  886. nautobot/ui/src/components/Table.js +48 -0
  887. nautobot/ui/src/components/TableItem.js +71 -0
  888. nautobot/ui/src/components/__tests__/AppFullWidthComponents.test.js +16 -0
  889. nautobot/ui/src/components/__tests__/AppTab.test.js +21 -0
  890. nautobot/ui/src/components/__tests__/Apps.test.js +14 -0
  891. nautobot/ui/src/components/__tests__/Layout.test.js +33 -0
  892. nautobot/ui/src/components/__tests__/Table.test.js +36 -0
  893. nautobot/ui/src/components/__tests__/TableItem.test.js +37 -0
  894. nautobot/ui/src/components/__tests__/paginator.test.js +43 -0
  895. nautobot/ui/src/components/__tests__/paginator_form.test.js +13 -0
  896. nautobot/ui/src/components/pagination.js +93 -0
  897. nautobot/ui/src/components/paginator.js +79 -0
  898. nautobot/ui/src/components/paginator_form.js +43 -0
  899. nautobot/ui/src/components/usePagination.js +57 -0
  900. nautobot/ui/src/constants/apiPath.js +10 -0
  901. nautobot/ui/src/constants/icons.js +15 -0
  902. nautobot/ui/src/constants/size.js +15 -0
  903. nautobot/ui/src/index.js +65 -0
  904. nautobot/ui/src/reportWebVitals.js +15 -0
  905. nautobot/ui/src/router.js +77 -0
  906. nautobot/ui/src/utils/api.js +131 -0
  907. nautobot/ui/src/utils/app-import.js +15 -0
  908. nautobot/ui/src/utils/color.js +15 -0
  909. nautobot/ui/src/utils/date.js +14 -0
  910. nautobot/ui/src/utils/index.js +15 -0
  911. nautobot/ui/src/utils/navigation.js +32 -0
  912. nautobot/ui/src/utils/session.js +64 -0
  913. nautobot/ui/src/utils/store.js +242 -0
  914. nautobot/ui/src/utils/string.js +6 -0
  915. nautobot/ui/src/utils/url.js +4 -0
  916. nautobot/ui/src/views/Home.js +138 -0
  917. nautobot/ui/src/views/InstalledApps.js +80 -0
  918. nautobot/ui/src/views/Login.js +48 -0
  919. nautobot/ui/src/views/Logout.js +20 -0
  920. nautobot/ui/src/views/__tests__/BSCreateViewTemplate.test.js +11 -0
  921. nautobot/ui/src/views/__tests__/BSListViewTemplate.test.js +107 -0
  922. nautobot/ui/src/views/__tests__/Login.test.js +15 -0
  923. nautobot/ui/src/views/generic/GenericView.js +142 -0
  924. nautobot/ui/src/views/generic/ObjectCreate.js +96 -0
  925. nautobot/ui/src/views/generic/ObjectList.js +127 -0
  926. nautobot/ui/src/views/generic/ObjectRetrieve.js +551 -0
  927. nautobot/users/admin.py +1 -1
  928. nautobot/users/api/serializers.py +51 -61
  929. nautobot/users/api/urls.py +1 -1
  930. nautobot/users/api/views.py +53 -2
  931. nautobot/users/migrations/0001_initial.py +0 -1
  932. nautobot/users/migrations/0002_token_ordering_by_created.py +0 -1
  933. nautobot/users/migrations/0003_alter_user_options.py +0 -1
  934. nautobot/users/migrations/0004_alter_user_managers.py +0 -1
  935. nautobot/users/tests/test_api.py +109 -28
  936. nautobot/users/tests/test_filters.py +0 -4
  937. nautobot/users/tests/test_models.py +0 -1
  938. nautobot/users/views.py +0 -7
  939. nautobot/virtualization/api/serializers.py +18 -132
  940. nautobot/virtualization/api/urls.py +1 -1
  941. nautobot/virtualization/api/views.py +1 -22
  942. nautobot/virtualization/choices.py +0 -2
  943. nautobot/virtualization/filters.py +12 -7
  944. nautobot/virtualization/forms.py +21 -117
  945. nautobot/virtualization/migrations/0001_initial.py +0 -1
  946. nautobot/virtualization/migrations/0002_virtualmachine_local_context_schema.py +0 -1
  947. nautobot/virtualization/migrations/0003_vminterface_verbose_name.py +0 -1
  948. nautobot/virtualization/migrations/0004_auto_slug.py +0 -1
  949. nautobot/virtualization/migrations/0005_add_natural_indexing.py +0 -1
  950. nautobot/virtualization/migrations/0006_vminterface_status.py +0 -1
  951. nautobot/virtualization/migrations/0007_vminterface_status_data_migration.py +0 -1
  952. nautobot/virtualization/migrations/0008_vminterface_parent.py +0 -1
  953. nautobot/virtualization/migrations/0009_cluster_location.py +0 -1
  954. nautobot/virtualization/migrations/0010_vminterface_mac_address_data_migration.py +0 -1
  955. nautobot/virtualization/migrations/0011_alter_vminterface_mac_address.py +0 -1
  956. nautobot/virtualization/migrations/0012_alter_virtualmachine_role_add_new_role.py +1 -2
  957. nautobot/virtualization/migrations/0013_migrate_virtualmachine_role_data.py +18 -12
  958. nautobot/virtualization/migrations/0014_rename_virtualmachine_roles.py +0 -1
  959. nautobot/virtualization/migrations/0015_rename_foreignkey_fields.py +1 -2
  960. nautobot/virtualization/migrations/0016_remove_site_foreign_key_from_cluster_class.py +0 -1
  961. nautobot/virtualization/migrations/0017_created_datetime.py +0 -1
  962. nautobot/virtualization/migrations/0018_related_name_changes.py +1 -2
  963. nautobot/virtualization/migrations/0019_vminterface_ip_addresses_m2m.py +0 -1
  964. nautobot/virtualization/migrations/0020_remove_clustergroup_clustertype_slug.py +20 -0
  965. nautobot/virtualization/migrations/0021_tagsfield_and_vminterface_to_primarymodel.py +39 -0
  966. nautobot/virtualization/migrations/0022_vminterface_timestamps_data_migration.py +17 -0
  967. nautobot/virtualization/migrations/0023_ipam__namespaces.py +25 -0
  968. nautobot/virtualization/migrations/0024_fixup_null_statuses.py +25 -0
  969. nautobot/virtualization/migrations/0025_status_nonnullable.py +29 -0
  970. nautobot/virtualization/models.py +39 -131
  971. nautobot/virtualization/navigation.py +18 -99
  972. nautobot/virtualization/tables.py +4 -4
  973. nautobot/virtualization/templates/virtualization/virtualmachine.html +13 -2
  974. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +6 -0
  975. nautobot/virtualization/tests/test_api.py +42 -52
  976. nautobot/virtualization/tests/test_filters.py +98 -75
  977. nautobot/virtualization/tests/test_models.py +36 -13
  978. nautobot/virtualization/tests/test_views.py +68 -73
  979. nautobot/virtualization/urls.py +10 -10
  980. nautobot/virtualization/views.py +8 -14
  981. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/METADATA +15 -22
  982. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/RECORD +987 -834
  983. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/WHEEL +1 -1
  984. nautobot/circuits/api/nested_serializers.py +0 -69
  985. nautobot/core/templates/plugin_template/navigation.py-tpl +0 -22
  986. nautobot/dcim/api/nested_serializers.py +0 -356
  987. nautobot/dcim/templates/dcim/device_import.html +0 -5
  988. nautobot/dcim/templates/dcim/device_import_child.html +0 -5
  989. nautobot/dcim/templates/dcim/inc/device_import_header.html +0 -4
  990. nautobot/extras/api/nested_serializers.py +0 -353
  991. nautobot/extras/migrations/0064_configcontext_data_migrations.py +0 -42
  992. nautobot/extras/migrations/0071_job__unique_name_data_migration.py +0 -47
  993. nautobot/extras/reports.py +0 -60
  994. nautobot/extras/scripts.py +0 -72
  995. nautobot/extras/tests/example_jobs/script_variables.py +0 -67
  996. nautobot/extras/tests/example_jobs/test_duplicate_name2.py +0 -5
  997. nautobot/extras/tests/example_jobs/test_fail.py +0 -16
  998. nautobot/extras/tests/example_jobs/test_file_upload_pass.py +0 -20
  999. nautobot/extras/tests/example_jobs/test_ipaddress_vars.py +0 -52
  1000. nautobot/extras/tests/example_jobs/test_job_button_receiver.py +0 -21
  1001. nautobot/extras/tests/example_jobs/test_job_hook_receiver.py +0 -20
  1002. nautobot/extras/tests/example_jobs/test_location_with_custom_field.py +0 -35
  1003. nautobot/extras/tests/example_jobs/test_log_redaction.py +0 -14
  1004. nautobot/extras/tests/example_jobs/test_modify_db.py +0 -19
  1005. nautobot/extras/tests/example_jobs/test_object_var_optional.py +0 -14
  1006. nautobot/extras/tests/example_jobs/test_object_var_required.py +0 -14
  1007. nautobot/extras/tests/example_jobs/test_object_vars.py +0 -29
  1008. nautobot/extras/tests/example_jobs/test_pass.py +0 -19
  1009. nautobot/extras/tests/example_jobs/test_read_only_fail.py +0 -24
  1010. nautobot/extras/tests/example_jobs/test_read_only_no_commit_field.py +0 -10
  1011. nautobot/extras/tests/example_jobs/test_read_only_pass.py +0 -22
  1012. nautobot/ipam/api/nested_serializers.py +0 -143
  1013. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js +0 -29
  1014. nautobot/project-static/docs/assets/javascripts/bundle.5a2dcb6a.min.js.map +0 -8
  1015. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js +0 -18
  1016. nautobot/project-static/docs/assets/javascripts/extra/bundle.5f09fbc3.min.js.map +0 -8
  1017. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css +0 -1
  1018. nautobot/project-static/docs/assets/stylesheets/extra.0d2c79a8.min.css.map +0 -1
  1019. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css +0 -1
  1020. nautobot/project-static/docs/assets/stylesheets/main.975780f9.min.css.map +0 -1
  1021. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css +0 -1
  1022. nautobot/project-static/docs/assets/stylesheets/palette.2505c338.min.css.map +0 -1
  1023. nautobot/tenancy/api/nested_serializers.py +0 -31
  1024. nautobot/users/api/nested_serializers.py +0 -67
  1025. nautobot/virtualization/api/nested_serializers.py +0 -65
  1026. /nautobot/extras/{tests/example_jobs → test_jobs}/__init__.py +0 -0
  1027. /nautobot/{dcim/models/sites.py → ipam/management/__init__.py} +0 -0
  1028. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/LICENSE.txt +0 -0
  1029. {nautobot-2.0.0a2.dist-info → nautobot-2.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,4 @@
1
- from datetime import datetime, timedelta
1
+ from datetime import timedelta
2
2
  import urllib.parse
3
3
  import uuid
4
4
 
@@ -19,7 +19,6 @@ from nautobot.dcim.tests import test_views
19
19
  from nautobot.extras.choices import (
20
20
  CustomFieldTypeChoices,
21
21
  JobExecutionType,
22
- JobSourceChoices,
23
22
  ObjectChangeActionChoices,
24
23
  SecretsGroupAccessTypeChoices,
25
24
  SecretsGroupSecretTypeChoices,
@@ -52,7 +51,7 @@ from nautobot.extras.models import (
52
51
  ComputedField,
53
52
  )
54
53
  from nautobot.extras.tests.test_relationships import RequiredRelationshipTestMixin
55
- from nautobot.extras.utils import get_job_content_type, TaggableClassesQuery
54
+ from nautobot.extras.utils import TaggableClassesQuery
56
55
  from nautobot.ipam.factory import VLANFactory
57
56
  from nautobot.ipam.models import VLAN, VLANGroup
58
57
  from nautobot.users.models import ObjectPermission
@@ -83,14 +82,14 @@ class ComputedFieldTestCase(
83
82
  ComputedField(
84
83
  content_type=obj_type,
85
84
  label="Computed Field One",
86
- slug="computed_field_one",
85
+ key="computed_field_one",
87
86
  template="Location name is {{ obj.name }}",
88
87
  fallback_value="Template error",
89
88
  weight=100,
90
89
  ),
91
90
  ComputedField(
92
91
  content_type=obj_type,
93
- slug="computed_field_two",
92
+ key="computed_field_two",
94
93
  label="Computed Field Two",
95
94
  template="Location name is {{ obj.name }}",
96
95
  fallback_value="Template error",
@@ -98,7 +97,7 @@ class ComputedFieldTestCase(
98
97
  ),
99
98
  ComputedField(
100
99
  content_type=obj_type,
101
- slug="computed_field_three",
100
+ key="computed_field_three",
102
101
  label="Computed Field Three",
103
102
  template="Location name is {{ obj.name }}",
104
103
  weight=100,
@@ -112,7 +111,8 @@ class ComputedFieldTestCase(
112
111
  ),
113
112
  )
114
113
  cls.location_type = LocationType.objects.get(name="Campus")
115
- cls.location1 = Location(name="NYC", location_type=cls.location_type)
114
+ status = Status.objects.get_for_model(Location).first()
115
+ cls.location1 = Location(name="NYC", location_type=cls.location_type, status=status)
116
116
  cls.location1.save()
117
117
 
118
118
  for cf in computed_fields:
@@ -120,7 +120,7 @@ class ComputedFieldTestCase(
120
120
 
121
121
  cls.form_data = {
122
122
  "content_type": obj_type.pk,
123
- "slug": "computed_field_four",
123
+ "key": "computed_field_four",
124
124
  "label": "Computed Field Four",
125
125
  "template": "{{ obj.name }} is the best Location!",
126
126
  "fallback_value": ":skull_emoji:",
@@ -286,7 +286,7 @@ class ConfigContextSchemaTestCase(
286
286
  cls.form_data = {
287
287
  "name": "Schema X",
288
288
  "slug": "schema-x",
289
- "data_schema": '{"type": "object", "properties": {"baz": {"type": "string"}}}',
289
+ "data_schema": '{"type": "object","properties": {"baz": {"type": "string"}}}', # Intentionally misformatted (missing space) to ensure proper formatting on output
290
290
  }
291
291
 
292
292
  cls.bulk_edit_data = {
@@ -449,7 +449,8 @@ class CustomLinkTest(TestCase):
449
449
  )
450
450
  customlink.save()
451
451
  location_type = LocationType.objects.get(name="Campus")
452
- location = Location(name="Test Location", slug="test-location", location_type=location_type)
452
+ status = Status.objects.get_for_model(Location).first()
453
+ location = Location(name="Test Location", slug="test-location", location_type=location_type, status=status)
453
454
  location.save()
454
455
 
455
456
  response = self.client.get(location.get_absolute_url(), follow=True)
@@ -477,13 +478,12 @@ class DynamicGroupTestCase(
477
478
  content_type = ContentType.objects.get_for_model(Device)
478
479
 
479
480
  # DynamicGroup objects to test.
480
- DynamicGroup.objects.create(name="DG 1", slug="dg-1", content_type=content_type)
481
- DynamicGroup.objects.create(name="DG 2", slug="dg-2", content_type=content_type)
482
- DynamicGroup.objects.create(name="DG 3", slug="dg-3", content_type=content_type)
481
+ DynamicGroup.objects.create(name="DG 1", content_type=content_type)
482
+ DynamicGroup.objects.create(name="DG 2", content_type=content_type)
483
+ DynamicGroup.objects.create(name="DG 3", content_type=content_type)
483
484
 
484
485
  cls.form_data = {
485
486
  "name": "new_dynamic_group",
486
- "slug": "new-dynamic-group",
487
487
  "description": "I am a new dynamic group object.",
488
488
  "content_type": content_type.pk,
489
489
  # Management form fields required for the dynamic formset
@@ -518,6 +518,17 @@ class DynamicGroupTestCase(
518
518
  instance.refresh_from_db()
519
519
  self.assertEqual(instance.filter, {"serial": data["filter-serial"]})
520
520
 
521
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
522
+ def test_filter_by_content_type(self):
523
+ """
524
+ Test that filtering by `content_type` in the UI succeeds.
525
+
526
+ This is a regression test for https://github.com/nautobot/nautobot/issues/3612
527
+ """
528
+ path = self._get_url("list")
529
+ response = self.client.get(path + "?content_type=dcim.device")
530
+ self.assertHttpStatus(response, 200)
531
+
521
532
 
522
533
  class ExportTemplateTestCase(
523
534
  ViewTestCases.CreateObjectViewTestCase,
@@ -572,27 +583,28 @@ class GitRepositoryTestCase(
572
583
  ViewTestCases.ListObjectsViewTestCase,
573
584
  ):
574
585
  model = GitRepository
586
+ slugify_function = staticmethod(slugify_dashes_to_underscores)
575
587
 
576
588
  @classmethod
577
589
  def setUpTestData(cls):
578
590
  secrets_groups = (
579
- SecretsGroup.objects.create(name="Secrets Group 1", slug="secrets-group-1"),
580
- SecretsGroup.objects.create(name="Secrets Group 2", slug="secrets-group-2"),
591
+ SecretsGroup.objects.create(name="Secrets Group 1"),
592
+ SecretsGroup.objects.create(name="Secrets Group 2"),
581
593
  )
582
594
 
583
595
  # Create four GitRepository records
584
596
  repos = (
585
- GitRepository(name="Repo 1", slug="repo-1", remote_url="https://example.com/repo1.git"),
586
- GitRepository(name="Repo 2", slug="repo-2", remote_url="https://example.com/repo2.git"),
587
- GitRepository(name="Repo 3", slug="repo-3", remote_url="https://example.com/repo3.git"),
597
+ GitRepository(name="Repo 1", slug="repo_1", remote_url="https://example.com/repo1.git"),
598
+ GitRepository(name="Repo 2", slug="repo_2", remote_url="https://example.com/repo2.git"),
599
+ GitRepository(name="Repo 3", slug="repo_3", remote_url="https://example.com/repo3.git"),
588
600
  GitRepository(name="Repo 4", remote_url="https://example.com/repo4.git", secrets_group=secrets_groups[0]),
589
601
  )
590
602
  for repo in repos:
591
- repo.save(trigger_resync=False)
603
+ repo.validated_save()
592
604
 
593
605
  cls.form_data = {
594
606
  "name": "A new Git repository",
595
- "slug": "a-new-git-repository",
607
+ "slug": "a_new_git_repository",
596
608
  "remote_url": "http://example.com/a_new_git_repository.git",
597
609
  "branch": "develop",
598
610
  "_token": "1234567890abcdef1234567890abcdef",
@@ -606,14 +618,28 @@ class GitRepositoryTestCase(
606
618
 
607
619
  cls.csv_data = (
608
620
  "name,slug,remote_url,branch,secrets_group,provided_contents",
609
- "Git Repository 5,git-repo-5,https://example.com,main,,extras.configcontext",
610
- "Git Repository 6,git-repo-6,https://example.com,develop,Secrets Group 2,",
611
- 'Git Repository 7,git-repo-7,https://example.com,next,Secrets Group 2,"extras.job,extras.configcontext"',
621
+ "Git Repository 5,git_repo_5,https://example.com,main,,extras.configcontext",
622
+ "Git Repository 6,git_repo_6,https://example.com,develop,Secrets Group 2,",
623
+ 'Git Repository 7,git_repo_7,https://example.com,next,Secrets Group 2,"extras.job,extras.configcontext"',
612
624
  )
613
625
 
614
626
  cls.slug_source = "name"
615
627
  cls.slug_test_object = "Repo 4"
616
628
 
629
+ def test_edit_object_with_permission(self):
630
+ instance = self._get_queryset().first()
631
+ form_data = self.form_data.copy()
632
+ form_data["slug"] = instance.slug # Slug is not editable
633
+ self.form_data = form_data
634
+ super().test_edit_object_with_permission()
635
+
636
+ def test_edit_object_with_constrained_permission(self):
637
+ instance = self._get_queryset().first()
638
+ form_data = self.form_data.copy()
639
+ form_data["slug"] = instance.slug # Slug is not editable
640
+ self.form_data = form_data
641
+ super().test_edit_object_with_constrained_permission()
642
+
617
643
 
618
644
  class NoteTestCase(
619
645
  ViewTestCases.CreateObjectViewTestCase,
@@ -716,21 +742,17 @@ class SecretTestCase(
716
742
 
717
743
  cls.form_data = {
718
744
  "name": "View Test 4",
719
- "slug": "view-test-4",
720
745
  "provider": "environment-variable",
721
746
  "parameters": '{"variable": "VIEW_TEST_4"}',
722
747
  }
723
748
 
724
749
  cls.csv_data = (
725
- "name,slug,provider,parameters",
726
- 'View Test 5,view-test-5,environment-variable,{"variable": "VIEW_TEST_5"}',
727
- 'View Test 6,,environment-variable,{"variable": "VIEW_TEST_6"}',
728
- 'View Test 7,,environment-variable,{"variable": "VIEW_TEST_7"}',
750
+ "name,provider,parameters",
751
+ 'View Test 5,environment-variable,{"variable": "VIEW_TEST_5"}',
752
+ 'View Test 6,environment-variable,{"variable": "VIEW_TEST_6"}',
753
+ 'View Test 7,environment-variable,{"variable": "VIEW_TEST_7"}',
729
754
  )
730
755
 
731
- cls.slug_source = "name"
732
- cls.slug_test_object = "View Test 3"
733
-
734
756
 
735
757
  # Not a full-fledged OrganizationalObjectViewTestCase as there's no BulkImportView for SecretsGroups
736
758
  class SecretsGroupTestCase(
@@ -747,15 +769,15 @@ class SecretsGroupTestCase(
747
769
  @classmethod
748
770
  def setUpTestData(cls):
749
771
  secrets_groups = (
750
- SecretsGroup.objects.create(name="Group 1", slug="group-1", description="First Group"),
751
- SecretsGroup.objects.create(name="Group 2", slug="group-2"),
752
- SecretsGroup.objects.create(name="Group 3", slug="group-3"),
772
+ SecretsGroup.objects.create(name="Group 1", description="First Group"),
773
+ SecretsGroup.objects.create(name="Group 2"),
774
+ SecretsGroup.objects.create(name="Group 3"),
753
775
  )
754
776
 
755
777
  secrets = (
756
- Secret.objects.create(name="secret 1", slug="secret-1", provider="text-file", parameters={"path": "/tmp"}),
757
- Secret.objects.create(name="secret 2", slug="secret-2", provider="text-file", parameters={"path": "/tmp"}),
758
- Secret.objects.create(name="secret 3", slug="secret-3", provider="text-file", parameters={"path": "/tmp"}),
778
+ Secret.objects.create(name="secret 1", provider="text-file", parameters={"path": "/tmp"}),
779
+ Secret.objects.create(name="secret 2", provider="text-file", parameters={"path": "/tmp"}),
780
+ Secret.objects.create(name="secret 3", provider="text-file", parameters={"path": "/tmp"}),
759
781
  )
760
782
 
761
783
  SecretsGroupAssociation.objects.create(
@@ -779,7 +801,6 @@ class SecretsGroupTestCase(
779
801
 
780
802
  cls.form_data = {
781
803
  "name": "Group 4",
782
- "slug": "group-4",
783
804
  "description": "Some description",
784
805
  # Management form fields required for the dynamic Secret formset
785
806
  "secrets_group_associations-TOTAL_FORMS": "0",
@@ -788,9 +809,6 @@ class SecretsGroupTestCase(
788
809
  "secrets_group_associations-MAX_NUM_FORMS": "1000",
789
810
  }
790
811
 
791
- cls.slug_source = "name"
792
- cls.slug_test_object = "Group 3"
793
-
794
812
 
795
813
  class GraphQLQueriesTestCase(
796
814
  ViewTestCases.CreateObjectViewTestCase,
@@ -807,17 +825,14 @@ class GraphQLQueriesTestCase(
807
825
  graphqlqueries = (
808
826
  GraphQLQuery(
809
827
  name="graphql-query-1",
810
- slug="graphql-query-1",
811
828
  query="{ query: locations {name} }",
812
829
  ),
813
830
  GraphQLQuery(
814
831
  name="graphql-query-2",
815
- slug="graphql-query-2",
816
832
  query='{ devices(role: "edge") { id, name, device_role { name slug } } }',
817
833
  ),
818
834
  GraphQLQuery(
819
835
  name="graphql-query-3",
820
- slug="graphql-query-3",
821
836
  query="""
822
837
  query ($device: String!) {
823
838
  devices(name: $device) {
@@ -920,7 +935,6 @@ query ($device: String!) {
920
935
 
921
936
  cls.form_data = {
922
937
  "name": "graphql-query-4",
923
- "slug": "graphql-query-4",
924
938
  "query": "{query: locations {name}}",
925
939
  }
926
940
 
@@ -946,27 +960,27 @@ class ScheduledJobTestCase(
946
960
  user = User.objects.create(username="user1", is_active=True)
947
961
  ScheduledJob.objects.create(
948
962
  name="test1",
949
- task="nautobot.extras.jobs.scheduled_job_handler",
950
- job_class="local/test_pass/TestPass",
963
+ task="pass.TestPass",
964
+ job_class="pass.TestPass",
951
965
  interval=JobExecutionType.TYPE_IMMEDIATELY,
952
966
  user=user,
953
- start_time=datetime.now(),
967
+ start_time=timezone.now(),
954
968
  )
955
969
  ScheduledJob.objects.create(
956
970
  name="test2",
957
- task="nautobot.extras.jobs.scheduled_job_handler",
958
- job_class="local/test_pass/TestPass",
971
+ task="pass.TestPass",
972
+ job_class="pass.TestPass",
959
973
  interval=JobExecutionType.TYPE_IMMEDIATELY,
960
974
  user=user,
961
- start_time=datetime.now(),
975
+ start_time=timezone.now(),
962
976
  )
963
977
  ScheduledJob.objects.create(
964
978
  name="test3",
965
- task="nautobot.extras.jobs.scheduled_job_handler",
966
- job_class="local/test_pass/TestPass",
979
+ task="pass.TestPass",
980
+ job_class="pass.TestPass",
967
981
  interval=JobExecutionType.TYPE_IMMEDIATELY,
968
982
  user=user,
969
- start_time=datetime.now(),
983
+ start_time=timezone.now(),
970
984
  )
971
985
 
972
986
  def test_only_enabled_is_listed(self):
@@ -976,11 +990,11 @@ class ScheduledJobTestCase(
976
990
  ScheduledJob.objects.create(
977
991
  enabled=False,
978
992
  name="test4",
979
- task="nautobot.extras.jobs.scheduled_job_handler",
980
- job_class="local/test_pass/TestPass",
993
+ task="pass.TestPass",
994
+ job_class="pass.TestPass",
981
995
  interval=JobExecutionType.TYPE_IMMEDIATELY,
982
996
  user=self.user,
983
- start_time=datetime.now(),
997
+ start_time=timezone.now(),
984
998
  )
985
999
 
986
1000
  response = self.client.get(self._get_url("list"))
@@ -994,8 +1008,8 @@ class ScheduledJobTestCase(
994
1008
  ScheduledJob.objects.create(
995
1009
  enabled=True,
996
1010
  name=name,
997
- task="nautobot.extras.jobs.scheduled_job_handler",
998
- job_class="local/test_pass/TestPass",
1011
+ task="pass.TestPass",
1012
+ job_class="pass.TestPass",
999
1013
  interval=JobExecutionType.TYPE_CUSTOM,
1000
1014
  user=self.user,
1001
1015
  start_time=timezone.now(),
@@ -1026,11 +1040,11 @@ class ScheduledJobTestCase(
1026
1040
  ScheduledJob.objects.create(
1027
1041
  enabled=True,
1028
1042
  name="test11",
1029
- task="nautobot.extras.jobs.scheduled_job_handler",
1030
- job_class="local/test_pass/TestPass",
1043
+ task="pass.TestPass",
1044
+ job_class="pass.TestPass",
1031
1045
  interval=JobExecutionType.TYPE_CUSTOM,
1032
1046
  user=self.user,
1033
- start_time=datetime.now(),
1047
+ start_time=timezone.now(),
1034
1048
  crontab="*/15 9,17 3 * 1-5",
1035
1049
  )
1036
1050
 
@@ -1057,39 +1071,28 @@ class ApprovalQueueTestCase(
1057
1071
 
1058
1072
  def setUp(self):
1059
1073
  super().setUp()
1060
- self.job_model = Job.objects.get_for_class_path("local/test_pass/TestPass")
1061
- self.job_model_2 = Job.objects.get_for_class_path("local/test_fail/TestFail")
1062
- self.job_model_3 = Job.objects.get_for_class_path("local/test_read_only_pass/TestReadOnlyPass")
1074
+ self.job_model = Job.objects.get_for_class_path("dry_run.TestDryRun")
1075
+ self.job_model_2 = Job.objects.get_for_class_path("fail.TestFail")
1063
1076
 
1064
1077
  ScheduledJob.objects.create(
1065
1078
  name="test1",
1066
- task="nautobot.extras.jobs.scheduled_job_handler",
1079
+ task="dry_run.TestDryRun",
1067
1080
  job_model=self.job_model,
1068
1081
  job_class=self.job_model.class_path,
1069
1082
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1070
1083
  user=self.user,
1071
1084
  approval_required=True,
1072
- start_time=datetime.now(),
1085
+ start_time=timezone.now(),
1073
1086
  )
1074
1087
  ScheduledJob.objects.create(
1075
1088
  name="test2",
1076
- task="nautobot.extras.jobs.scheduled_job_handler",
1089
+ task="fail.TestFail",
1077
1090
  job_model=self.job_model_2,
1078
1091
  job_class=self.job_model_2.class_path,
1079
1092
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1080
1093
  user=self.user,
1081
1094
  approval_required=True,
1082
- start_time=datetime.now(),
1083
- )
1084
- ScheduledJob.objects.create(
1085
- name="test3",
1086
- task="nautobot.extras.jobs.scheduled_job_handler",
1087
- job_model=self.job_model_3,
1088
- job_class=self.job_model_3.class_path,
1089
- interval=JobExecutionType.TYPE_IMMEDIATELY,
1090
- user=self.user,
1091
- approval_required=True,
1092
- start_time=datetime.now(),
1095
+ start_time=timezone.now(),
1093
1096
  )
1094
1097
 
1095
1098
  def test_only_approvable_is_listed(self):
@@ -1097,13 +1100,13 @@ class ApprovalQueueTestCase(
1097
1100
 
1098
1101
  ScheduledJob.objects.create(
1099
1102
  name="test4",
1100
- task="nautobot.extras.jobs.scheduled_job_handler",
1103
+ task="pass.TestPass",
1101
1104
  job_model=self.job_model,
1102
1105
  job_class=self.job_model.class_path,
1103
1106
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1104
1107
  user=self.user,
1105
1108
  approval_required=False,
1106
- start_time=datetime.now(),
1109
+ start_time=timezone.now(),
1107
1110
  )
1108
1111
 
1109
1112
  response = self.client.get(self._get_url("list"))
@@ -1241,10 +1244,30 @@ class ApprovalQueueTestCase(
1241
1244
 
1242
1245
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1243
1246
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1244
- def test_post_dry_run_success(self, _):
1247
+ def test_post_dry_run_not_supported(self, _):
1248
+ """Request a dry run on a job that doesn't support dryrun."""
1249
+ self.add_permissions("extras.view_scheduledjob")
1250
+ instance = ScheduledJob.objects.filter(name="test2").first()
1251
+ instance.job_model.enabled = True
1252
+ instance.job_model.save()
1253
+ obj_perm = ObjectPermission(name="Test permission", constraints={"pk": instance.job_model.pk}, actions=["run"])
1254
+ obj_perm.save()
1255
+ obj_perm.users.add(self.user)
1256
+ obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
1257
+ data = {"_dry_run": True}
1258
+
1259
+ response = self.client.post(self._get_url("view", instance), data)
1260
+ # Job was not submitted
1261
+ self.assertFalse(JobResult.objects.filter(name=instance.job_model.class_path).exists())
1262
+ self.assertContains(response, "This job does not support dryrun")
1263
+
1264
+ @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
1265
+ @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1266
+ @mock.patch("nautobot.extras.models.jobs.JobResult.enqueue_job")
1267
+ def test_post_dry_run_success(self, mock_enqueue_job, _):
1245
1268
  """Successfully request a dry run based on object-based run_job permissions."""
1246
1269
  self.add_permissions("extras.view_scheduledjob")
1247
- instance = self._get_queryset().first()
1270
+ instance = ScheduledJob.objects.filter(name="test1").first()
1248
1271
  instance.job_model.enabled = True
1249
1272
  instance.job_model.save()
1250
1273
  obj_perm = ObjectPermission(name="Test permission", constraints={"pk": instance.job_model.pk}, actions=["run"])
@@ -1253,15 +1276,12 @@ class ApprovalQueueTestCase(
1253
1276
  obj_perm.object_types.add(ContentType.objects.get_for_model(Job))
1254
1277
  data = {"_dry_run": True}
1255
1278
 
1279
+ mock_enqueue_job.side_effect = lambda job_model, *args, **kwargs: JobResult.objects.create(name=job_model.name)
1280
+
1256
1281
  response = self.client.post(self._get_url("view", instance), data)
1257
1282
  # Job was submitted
1258
- self.assertTrue(
1259
- JobResult.objects.filter(name=instance.job_model.class_path).exists(),
1260
- msg=extract_page_body(response.content.decode(response.charset)),
1261
- )
1262
- job_result = JobResult.objects.get(name=instance.job_model.class_path)
1263
- self.assertEqual(job_result.job_model, instance.job_model)
1264
- self.assertEqual(job_result.user, self.user)
1283
+ mock_enqueue_job.assert_called_once()
1284
+ job_result = JobResult.objects.get(name=instance.job_model.name)
1265
1285
  self.assertRedirects(response, reverse("extras:jobresult", kwargs={"pk": job_result.pk}))
1266
1286
 
1267
1287
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -1435,22 +1455,8 @@ class JobResultTestCase(
1435
1455
 
1436
1456
  @classmethod
1437
1457
  def setUpTestData(cls):
1438
- obj_type = get_job_content_type()
1439
- JobResult.objects.create(
1440
- name="local/test_pass/TestPass",
1441
- task_id=uuid.uuid4(),
1442
- obj_type=obj_type,
1443
- )
1444
- JobResult.objects.create(
1445
- name="local/test_fail/TestFail",
1446
- task_id=uuid.uuid4(),
1447
- obj_type=obj_type,
1448
- )
1449
- JobResult.objects.create(
1450
- name="local/test_read_only_fail/TestReadOnlyFail",
1451
- task_id=uuid.uuid4(),
1452
- obj_type=obj_type,
1453
- )
1458
+ JobResult.objects.create(name="pass.TestPass")
1459
+ JobResult.objects.create(name="fail.TestFail")
1454
1460
 
1455
1461
 
1456
1462
  class JobTestCase(
@@ -1485,8 +1491,8 @@ class JobTestCase(
1485
1491
  cls.run_urls = (
1486
1492
  # Legacy URL (job class path based)
1487
1493
  reverse("extras:job", kwargs={"class_path": cls.test_pass.class_path}),
1488
- # Current URL (job model slug based)
1489
- reverse("extras:job_run", kwargs={"slug": cls.test_pass.slug}),
1494
+ # Current URL (job model pk based)
1495
+ reverse("extras:job_run", kwargs={"pk": cls.test_pass.pk}),
1490
1496
  )
1491
1497
 
1492
1498
  cls.test_required_args = Job.objects.get(job_class_name="TestRequired")
@@ -1496,13 +1502,12 @@ class JobTestCase(
1496
1502
  cls.extra_run_urls = (
1497
1503
  # Legacy URL (job class path based)
1498
1504
  reverse("extras:job", kwargs={"class_path": cls.test_required_args.class_path}),
1499
- # Current URL (job model slug based)
1500
- reverse("extras:job_run", kwargs={"slug": cls.test_required_args.slug}),
1505
+ # Current URL (job model pk based)
1506
+ reverse("extras:job_run", kwargs={"pk": cls.test_required_args.pk}),
1501
1507
  )
1502
1508
 
1503
1509
  # Create an entry for a non-installed Job as well
1504
1510
  cls.test_not_installed = Job(
1505
- source=JobSourceChoices.SOURCE_LOCAL,
1506
1511
  module_name="nonexistent",
1507
1512
  job_class_name="NoSuchJob",
1508
1513
  grouping="Nonexistent Jobs",
@@ -1517,7 +1522,6 @@ class JobTestCase(
1517
1522
  }
1518
1523
 
1519
1524
  cls.form_data = {
1520
- "slug": "custom-job-slug",
1521
1525
  "enabled": True,
1522
1526
  "grouping_override": True,
1523
1527
  "grouping": "Overridden Grouping",
@@ -1525,12 +1529,10 @@ class JobTestCase(
1525
1529
  "name": "Overridden Name",
1526
1530
  "description_override": True,
1527
1531
  "description": "This is an overridden description of a job.",
1528
- "commit_default_override": True,
1529
- "commit_default": False,
1532
+ "dryrun_default_override": True,
1533
+ "dryrun_default": True,
1530
1534
  "hidden_override": True,
1531
1535
  "hidden": False,
1532
- "read_only_override": True,
1533
- "read_only": False,
1534
1536
  "approval_required_override": True,
1535
1537
  "approval_required": True,
1536
1538
  "soft_time_limit_override": True,
@@ -1656,7 +1658,7 @@ class JobTestCase(
1656
1658
 
1657
1659
  for run_url in (
1658
1660
  reverse("extras:job", kwargs={"class_path": self.test_not_installed.class_path}),
1659
- reverse("extras:job_run", kwargs={"slug": self.test_not_installed.slug}),
1661
+ reverse("extras:job_run", kwargs={"pk": self.test_not_installed.pk}),
1660
1662
  ):
1661
1663
  response = self.client.post(run_url, self.data_run_immediately)
1662
1664
  self.assertEqual(response.status_code, 200, msg=run_url)
@@ -1670,14 +1672,14 @@ class JobTestCase(
1670
1672
  self.add_permissions("extras.run_job")
1671
1673
 
1672
1674
  for run_url in (
1673
- reverse("extras:job", kwargs={"class_path": "local/test_fail/TestFail"}),
1674
- reverse("extras:job_run", kwargs={"slug": Job.objects.get(job_class_name="TestFail").slug}),
1675
+ reverse("extras:job", kwargs={"class_path": "fail.TestFail"}),
1676
+ reverse("extras:job_run", kwargs={"pk": Job.objects.get(job_class_name="TestFail").pk}),
1675
1677
  ):
1676
1678
  response = self.client.post(run_url, self.data_run_immediately)
1677
1679
  self.assertEqual(response.status_code, 200, msg=run_url)
1678
1680
  response_body = extract_page_body(response.content.decode(response.charset))
1679
1681
  self.assertIn("Job is not enabled to be run", response_body)
1680
- self.assertFalse(JobResult.objects.filter(name="local/test_fail/TestFail").exists())
1682
+ self.assertFalse(JobResult.objects.filter(name="fail.TestFail").exists())
1681
1683
 
1682
1684
  def test_run_now_missing_args(self):
1683
1685
  self.add_permissions("extras.run_job")
@@ -1705,6 +1707,37 @@ class JobTestCase(
1705
1707
  result = JobResult.objects.latest()
1706
1708
  self.assertRedirects(response, reverse("extras:jobresult", kwargs={"pk": result.pk}))
1707
1709
 
1710
+ @mock.patch("nautobot.extras.jobs.task_queues_as_choices")
1711
+ def test_rerun_job(self, mock_task_queues_as_choices):
1712
+ self.add_permissions("extras.run_job")
1713
+ self.add_permissions("extras.view_jobresult")
1714
+
1715
+ mock_task_queues_as_choices.return_value = [("default", ""), ("queue1", ""), ("uniquequeue", "")]
1716
+ job_celery_kwargs = {
1717
+ "nautobot_job_job_model_id": self.test_required_args.id,
1718
+ "nautobot_job_profile": True,
1719
+ "nautobot_job_user_id": self.user.id,
1720
+ "queue": "uniquequeue",
1721
+ }
1722
+
1723
+ previous_result = JobResult.objects.create(
1724
+ job_model=self.test_required_args,
1725
+ user=self.user,
1726
+ task_kwargs={"var": "456"},
1727
+ celery_kwargs=job_celery_kwargs,
1728
+ )
1729
+
1730
+ run_url = reverse("extras:job_run", kwargs={"pk": self.test_required_args.pk})
1731
+ response = self.client.get(f"{run_url}?kwargs_from_job_result={previous_result.pk!s}")
1732
+ content = extract_page_body(response.content.decode(response.charset))
1733
+
1734
+ self.assertInHTML('<option value="uniquequeue" selected>', content)
1735
+ self.assertInHTML(
1736
+ '<input type="text" name="var" value="456" class="form-control form-control" required placeholder="None" id="id_var">',
1737
+ content,
1738
+ )
1739
+ self.assertInHTML('<input type="hidden" name="_profile" value="True" id="id__profile">', content)
1740
+
1708
1741
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1709
1742
  def test_run_later_missing_name(self, _):
1710
1743
  self.add_permissions("extras.run_job")
@@ -1748,7 +1781,7 @@ class JobTestCase(
1748
1781
  data = {
1749
1782
  "_schedule_type": "future",
1750
1783
  "_schedule_name": "test",
1751
- "_schedule_start_time": str(datetime.now() - timedelta(minutes=1)),
1784
+ "_schedule_start_time": str(timezone.now() - timedelta(minutes=1)),
1752
1785
  }
1753
1786
 
1754
1787
  for run_url in self.run_urls:
@@ -1783,25 +1816,6 @@ class JobTestCase(
1783
1816
  self.assertEqual(scheduled.name, "test")
1784
1817
  self.assertEqual(scheduled.start_time, start_time)
1785
1818
 
1786
- @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1787
- def test_run_later_sets_scheduled_job_kwargs_pk(self, _):
1788
- self.add_permissions("extras.run_job")
1789
- self.add_permissions("extras.view_scheduledjob")
1790
-
1791
- start_time = timezone.now() + timedelta(minutes=1)
1792
- data = {
1793
- "_schedule_type": "future",
1794
- "_schedule_name": "test",
1795
- "_schedule_start_time": str(start_time),
1796
- }
1797
-
1798
- for run_url in self.run_urls:
1799
- response = self.client.post(run_url, data)
1800
- self.assertRedirects(response, reverse("extras:scheduledjob_list"))
1801
-
1802
- scheduled = ScheduledJob.objects.last()
1803
- self.assertEqual(scheduled.kwargs["scheduled_job_pk"], str(scheduled.pk))
1804
-
1805
1819
  @mock.patch("nautobot.extras.views.get_worker_count", return_value=1)
1806
1820
  def test_run_job_with_sensitive_variables_for_future(self, _):
1807
1821
  self.add_permissions("extras.run_job")
@@ -1957,7 +1971,8 @@ class ObjectChangeTestCase(TestCase):
1957
1971
  @classmethod
1958
1972
  def setUpTestData(cls):
1959
1973
  location_type = LocationType.objects.get(name="Campus")
1960
- location = Location(name="Location 1", slug="location-1", location_type=location_type)
1974
+ location_status = Status.objects.get_for_model(Location).first()
1975
+ location = Location(name="Location 1", slug="location-1", location_type=location_type, status=location_status)
1961
1976
  location.save()
1962
1977
 
1963
1978
  # Create three ObjectChanges
@@ -1994,7 +2009,7 @@ class RelationshipTestCase(
1994
2009
  RequiredRelationshipTestMixin,
1995
2010
  ):
1996
2011
  model = Relationship
1997
- slug_source = "name"
2012
+ slug_source = "label"
1998
2013
  slugify_function = staticmethod(slugify_dashes_to_underscores)
1999
2014
 
2000
2015
  @classmethod
@@ -2002,36 +2017,37 @@ class RelationshipTestCase(
2002
2017
  interface_type = ContentType.objects.get_for_model(Interface)
2003
2018
  device_type = ContentType.objects.get_for_model(Device)
2004
2019
  vlan_type = ContentType.objects.get_for_model(VLAN)
2020
+ status = Status.objects.get_for_model(Interface).first()
2005
2021
 
2006
2022
  Relationship(
2007
- name="Device VLANs",
2008
- slug="device-vlans",
2023
+ label="Device VLANs",
2024
+ key="device_vlans",
2009
2025
  type="many-to-many",
2010
2026
  source_type=device_type,
2011
2027
  destination_type=vlan_type,
2012
2028
  ).validated_save()
2013
2029
  Relationship(
2014
- name="Primary VLAN",
2015
- slug="primary-vlan",
2030
+ label="Primary VLAN",
2031
+ key="primary_vlan",
2016
2032
  type="one-to-many",
2017
2033
  source_type=vlan_type,
2018
2034
  destination_type=device_type,
2019
2035
  ).validated_save()
2020
2036
  Relationship(
2021
- name="Primary Interface",
2037
+ label="Primary Interface",
2022
2038
  type="one-to-one",
2023
2039
  source_type=device_type,
2024
2040
  destination_type=interface_type,
2025
2041
  ).validated_save()
2026
2042
 
2027
2043
  cls.form_data = {
2028
- "name": "VLAN-to-Interface",
2029
- "slug": "vlan-to-interface",
2044
+ "label": "VLAN-to-Interface",
2045
+ "key": "vlan_to_interface",
2030
2046
  "type": "many-to-many",
2031
2047
  "source_type": vlan_type.pk,
2032
2048
  "source_label": "Interfaces",
2033
2049
  "source_hidden": False,
2034
- "source_filter": '{"status": ["active"]}',
2050
+ "source_filter": '{"status": ["' + status.name + '"]}',
2035
2051
  "destination_type": interface_type.pk,
2036
2052
  "destination_label": "VLANs",
2037
2053
  "destination_hidden": True,
@@ -2091,7 +2107,7 @@ class RelationshipTestCase(
2091
2107
  reverse("ipam:vlan_bulk_edit"),
2092
2108
  data={
2093
2109
  "pk": [str(vlan.id) for vlan in vlans],
2094
- "add_cr_vlans-devices-m2m__source": [str(device_for_association.id)],
2110
+ "add_cr_vlans_devices_m2m__source": [str(device_for_association.id)],
2095
2111
  "_apply": [""],
2096
2112
  },
2097
2113
  follow=True,
@@ -2103,7 +2119,7 @@ class RelationshipTestCase(
2103
2119
  reverse("ipam:vlan_bulk_edit"),
2104
2120
  data={
2105
2121
  "pk": [str(vlan.id) for vlan in vlans],
2106
- "remove_cr_vlans-devices-m2m__source": [str(device_for_association.id)],
2122
+ "remove_cr_vlans_devices_m2m__source": [str(device_for_association.id)],
2107
2123
  "_apply": [""],
2108
2124
  },
2109
2125
  )
@@ -2129,26 +2145,35 @@ class RelationshipAssociationTestCase(
2129
2145
  vlan_type = ContentType.objects.get_for_model(VLAN)
2130
2146
 
2131
2147
  relationship = Relationship(
2132
- name="Device VLANs",
2133
- slug="device-vlans",
2148
+ label="Device VLANs",
2149
+ key="device_vlans",
2134
2150
  type="many-to-many",
2135
2151
  source_type=device_type,
2136
2152
  destination_type=vlan_type,
2137
2153
  )
2154
+ cls.relationship = relationship
2138
2155
  relationship.validated_save()
2139
- manufacturer = Manufacturer.objects.create(name="Manufacturer 1", slug="manufacturer-1")
2156
+ manufacturer = Manufacturer.objects.first()
2140
2157
  devicetype = DeviceType.objects.create(manufacturer=manufacturer, model="Device Type 1", slug="device-type-1")
2141
2158
  devicerole = Role.objects.get_for_model(Device).first()
2159
+ devicestatus = Status.objects.get_for_model(Device).first()
2142
2160
  location = Location.objects.first()
2143
2161
  devices = (
2144
- Device.objects.create(name="Device 1", device_type=devicetype, role=devicerole, location=location),
2145
- Device.objects.create(name="Device 2", device_type=devicetype, role=devicerole, location=location),
2146
- Device.objects.create(name="Device 3", device_type=devicetype, role=devicerole, location=location),
2162
+ Device.objects.create(
2163
+ name="Device 1", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2164
+ ),
2165
+ Device.objects.create(
2166
+ name="Device 2", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2167
+ ),
2168
+ Device.objects.create(
2169
+ name="Device 3", device_type=devicetype, role=devicerole, location=location, status=devicestatus
2170
+ ),
2147
2171
  )
2172
+ vlan_status = Status.objects.get_for_model(VLAN).first()
2148
2173
  vlans = (
2149
- VLAN.objects.create(vid=1, name="VLAN 1"),
2150
- VLAN.objects.create(vid=2, name="VLAN 2"),
2151
- VLAN.objects.create(vid=3, name="VLAN 3"),
2174
+ VLAN.objects.create(vid=1, name="VLAN 1", status=vlan_status),
2175
+ VLAN.objects.create(vid=2, name="VLAN 2", status=vlan_status),
2176
+ VLAN.objects.create(vid=3, name="VLAN 3", status=vlan_status),
2152
2177
  )
2153
2178
 
2154
2179
  RelationshipAssociation(
@@ -2173,6 +2198,28 @@ class RelationshipAssociationTestCase(
2173
2198
  destination_id=vlans[2].pk,
2174
2199
  ).validated_save()
2175
2200
 
2201
+ def test_list_objects_with_constrained_permission(self):
2202
+ instance1, instance2 = self.relationship.relationship_associations.all()[:2]
2203
+
2204
+ # Add object-level permission
2205
+ obj_perm = ObjectPermission(
2206
+ name="Test permission",
2207
+ constraints={"pk": instance1.pk},
2208
+ actions=["view"],
2209
+ )
2210
+ obj_perm.save()
2211
+ obj_perm.users.add(self.user)
2212
+ obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
2213
+
2214
+ response = self.client.get(self._get_url("list"))
2215
+ self.assertHttpStatus(response, 200)
2216
+ content = extract_page_body(response.content.decode(response.charset))
2217
+ # TODO: it'd make test failures more readable if we strip the page headers/footers from the content
2218
+ self.assertIn(instance1.source.name, content, msg=content)
2219
+ self.assertIn(instance1.destination.name, content, msg=content)
2220
+ self.assertNotIn(instance2.source.name, content, msg=content)
2221
+ self.assertNotIn(instance2.destination.name, content, msg=content)
2222
+
2176
2223
 
2177
2224
  class StatusTestCase(
2178
2225
  ViewTestCases.CreateObjectViewTestCase,
@@ -2191,27 +2238,23 @@ class StatusTestCase(
2191
2238
 
2192
2239
  cls.form_data = {
2193
2240
  "name": "new_status",
2194
- "slug": "new-status",
2195
2241
  "description": "I am a new status object.",
2196
2242
  "color": "ffcc00",
2197
2243
  "content_types": [content_type.pk],
2198
2244
  }
2199
2245
 
2200
2246
  cls.csv_data = (
2201
- "name,slug,color,content_types"
2202
- 'test_status1,test-status1,ffffff,"dcim.device"'
2203
- 'test_status2,test-status2,ffffff,"dcim.device,dcim.rack"'
2204
- 'test_status3,test-status3,ffffff,"dcim.device,dcim.location"'
2205
- 'test_status4,,ffffff,"dcim.device,dcim.location"'
2247
+ "name,color,content_types"
2248
+ "test_status1,ffffff,dcim.device"
2249
+ 'test_status2,ffffff,"dcim.device,dcim.location"'
2250
+ "test_status3,ffffff,dcim.device"
2251
+ "test_status4,ffffff,dcim.device"
2206
2252
  )
2207
2253
 
2208
2254
  cls.bulk_edit_data = {
2209
2255
  "color": "000000",
2210
2256
  }
2211
2257
 
2212
- cls.slug_source = "name"
2213
- cls.slug_test_object = Status.objects.first().name
2214
-
2215
2258
 
2216
2259
  class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
2217
2260
  model = Tag
@@ -2227,10 +2270,10 @@ class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
2227
2270
  }
2228
2271
 
2229
2272
  cls.csv_data = (
2230
- "name,slug,color,description",
2231
- "Tag 4,tag-4,ff0000,Fourth tag",
2232
- "Tag 5,tag-5,00ff00,Fifth tag",
2233
- "Tag 6,tag-6,0000ff,Sixth tag",
2273
+ "name,slug,color,description,content_types",
2274
+ "Tag 4,tag-4,ff0000,Fourth tag,dcim.device",
2275
+ 'Tag 5,tag-5,00ff00,Fifth tag,"dcim.device,dcim.location"',
2276
+ "Tag 6,tag-6,0000ff,Sixth tag,dcim.location",
2234
2277
  )
2235
2278
 
2236
2279
  cls.bulk_edit_data = {
@@ -2364,23 +2407,19 @@ class RoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
2364
2407
 
2365
2408
  cls.form_data = {
2366
2409
  "name": "New Role",
2367
- "slug": "new-role",
2368
2410
  "description": "I am a new role object.",
2369
2411
  "color": ColorChoices.COLOR_GREY,
2370
2412
  "content_types": [content_type.pk],
2371
2413
  }
2372
2414
 
2373
2415
  cls.csv_data = (
2374
- "name,slug,weight,color,content_types",
2375
- 'test_role1,test-role1,1000,ffffff,"dcim.device"',
2376
- 'test_role2,test-role2,200,ffffff,"dcim.device,dcim.rack"',
2377
- 'test_role3,test-role3,100,ffffff,"dcim.device,ipam.prefix"',
2378
- 'test_role4,test-role4,50,ffffff,"ipam.ipaddress,ipam.vlan"',
2416
+ "name,weight,color,content_types,description",
2417
+ "test_role1,1000,ffffff,dcim.device,A Role",
2418
+ 'test_role2,200,ffffff,"dcim.device,dcim.rack",A Role',
2419
+ 'test_role3,100,ffffff,"dcim.device,ipam.prefix",A Role',
2420
+ 'test_role4,50,ffffff,"ipam.ipaddress,ipam.vlan",A Role',
2379
2421
  )
2380
2422
 
2381
2423
  cls.bulk_edit_data = {
2382
2424
  "color": "000000",
2383
2425
  }
2384
-
2385
- cls.slug_source = "name"
2386
- cls.slug_test_object = Role.objects.first().name