nautobot 2.4.0b1__py3-none-any.whl → 2.4.2__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.

Potentially problematic release.


This version of nautobot might be problematic. Click here for more details.

Files changed (668) hide show
  1. nautobot/apps/__init__.py +1 -1
  2. nautobot/apps/api.py +8 -8
  3. nautobot/apps/change_logging.py +2 -2
  4. nautobot/apps/choices.py +4 -4
  5. nautobot/apps/events.py +3 -3
  6. nautobot/apps/factory.py +2 -2
  7. nautobot/apps/filters.py +1 -1
  8. nautobot/apps/forms.py +20 -20
  9. nautobot/apps/graphql.py +2 -2
  10. nautobot/apps/jobs.py +8 -8
  11. nautobot/apps/models.py +19 -19
  12. nautobot/apps/tables.py +1 -1
  13. nautobot/apps/testing.py +10 -10
  14. nautobot/apps/ui.py +2 -2
  15. nautobot/apps/utils.py +7 -7
  16. nautobot/apps/views.py +7 -7
  17. nautobot/circuits/api/serializers.py +1 -0
  18. nautobot/circuits/api/views.py +4 -8
  19. nautobot/circuits/tables.py +2 -1
  20. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  21. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
  22. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  23. nautobot/circuits/views.py +3 -3
  24. nautobot/cloud/api/views.py +6 -10
  25. nautobot/cloud/models.py +1 -1
  26. nautobot/cloud/views.py +0 -16
  27. nautobot/core/api/constants.py +11 -0
  28. nautobot/core/api/fields.py +5 -5
  29. nautobot/core/api/filter_backends.py +3 -9
  30. nautobot/core/api/schema.py +13 -2
  31. nautobot/core/api/serializers.py +40 -34
  32. nautobot/core/api/views.py +56 -4
  33. nautobot/core/apps/__init__.py +0 -5
  34. nautobot/core/celery/log.py +4 -4
  35. nautobot/core/celery/schedulers.py +2 -2
  36. nautobot/core/choices.py +2 -2
  37. nautobot/core/events/__init__.py +3 -3
  38. nautobot/core/filters.py +67 -35
  39. nautobot/core/forms/__init__.py +19 -19
  40. nautobot/core/forms/fields.py +14 -11
  41. nautobot/core/forms/forms.py +33 -2
  42. nautobot/core/graphql/types.py +1 -1
  43. nautobot/core/jobs/__init__.py +28 -7
  44. nautobot/core/jobs/bulk_actions.py +285 -0
  45. nautobot/core/jobs/cleanup.py +48 -12
  46. nautobot/core/jobs/groups.py +1 -1
  47. nautobot/core/management/commands/validate_models.py +1 -1
  48. nautobot/core/models/__init__.py +3 -1
  49. nautobot/core/models/query_functions.py +2 -2
  50. nautobot/core/models/tree_queries.py +6 -3
  51. nautobot/core/settings.py +29 -2
  52. nautobot/core/settings.yaml +21 -0
  53. nautobot/core/tables.py +79 -61
  54. nautobot/core/templates/about.html +67 -0
  55. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  56. nautobot/core/templates/inc/media.html +3 -0
  57. nautobot/core/templates/inc/nav_menu.html +1 -0
  58. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  59. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  60. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  61. nautobot/core/templates/search.html +7 -0
  62. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  63. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  64. nautobot/core/templates/utilities/theme_preview.html +7 -0
  65. nautobot/core/templatetags/helpers.py +11 -2
  66. nautobot/core/testing/__init__.py +8 -8
  67. nautobot/core/testing/api.py +170 -15
  68. nautobot/core/testing/filters.py +45 -10
  69. nautobot/core/testing/forms.py +2 -0
  70. nautobot/core/testing/integration.py +514 -5
  71. nautobot/core/testing/mixins.py +7 -2
  72. nautobot/core/testing/views.py +44 -29
  73. nautobot/core/tests/integration/test_app_home.py +0 -1
  74. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  75. nautobot/core/tests/integration/test_filters.py +0 -2
  76. nautobot/core/tests/integration/test_home.py +0 -1
  77. nautobot/core/tests/integration/test_navbar.py +0 -1
  78. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  79. nautobot/core/tests/runner.py +1 -1
  80. nautobot/core/tests/test_api.py +98 -1
  81. nautobot/core/tests/test_csv.py +25 -3
  82. nautobot/core/tests/test_filters.py +209 -246
  83. nautobot/core/tests/test_forms.py +1 -0
  84. nautobot/core/tests/test_jobs.py +492 -1
  85. nautobot/core/tests/test_models.py +9 -0
  86. nautobot/core/tests/test_settings_schema.py +7 -0
  87. nautobot/core/tests/test_tables.py +100 -0
  88. nautobot/core/tests/test_utils.py +63 -1
  89. nautobot/core/tests/test_views.py +30 -3
  90. nautobot/core/ui/nav.py +1 -0
  91. nautobot/core/ui/object_detail.py +15 -1
  92. nautobot/core/urls.py +11 -0
  93. nautobot/core/utils/git.py +7 -2
  94. nautobot/core/utils/lookup.py +11 -8
  95. nautobot/core/utils/querysets.py +64 -0
  96. nautobot/core/utils/requests.py +24 -9
  97. nautobot/core/views/__init__.py +42 -0
  98. nautobot/core/views/generic.py +131 -197
  99. nautobot/core/views/mixins.py +136 -41
  100. nautobot/core/views/renderers.py +6 -6
  101. nautobot/core/views/utils.py +2 -2
  102. nautobot/dcim/api/serializers.py +56 -64
  103. nautobot/dcim/api/views.py +47 -113
  104. nautobot/dcim/constants.py +6 -13
  105. nautobot/dcim/factory.py +6 -1
  106. nautobot/dcim/filters/__init__.py +31 -2
  107. nautobot/dcim/forms.py +48 -17
  108. nautobot/dcim/graphql/types.py +2 -2
  109. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  110. nautobot/dcim/models/__init__.py +1 -1
  111. nautobot/dcim/models/device_component_templates.py +2 -2
  112. nautobot/dcim/models/device_components.py +22 -20
  113. nautobot/dcim/models/devices.py +10 -1
  114. nautobot/dcim/models/locations.py +3 -3
  115. nautobot/dcim/models/power.py +6 -5
  116. nautobot/dcim/models/racks.py +4 -4
  117. nautobot/dcim/tables/__init__.py +3 -3
  118. nautobot/dcim/tables/devices.py +9 -6
  119. nautobot/dcim/tables/devicetypes.py +2 -2
  120. nautobot/dcim/tables/racks.py +1 -1
  121. nautobot/dcim/templates/dcim/cable.html +1 -1
  122. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  123. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  124. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  125. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  126. nautobot/dcim/templates/dcim/device/base.html +1 -1
  127. nautobot/dcim/templates/dcim/device.html +3 -11
  128. nautobot/dcim/templates/dcim/device_component.html +1 -1
  129. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  130. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  131. nautobot/dcim/templates/dcim/location.html +2 -10
  132. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  133. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  134. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  135. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  136. nautobot/dcim/templates/dcim/platform.html +1 -1
  137. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  138. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  139. nautobot/dcim/templates/dcim/rack.html +2 -10
  140. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  141. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  142. nautobot/dcim/templates/dcim/rackreservation.html +3 -11
  143. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  144. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  145. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  146. nautobot/dcim/tests/integration/test_controller.py +62 -0
  147. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  148. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  149. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  150. nautobot/dcim/tests/test_api.py +16 -5
  151. nautobot/dcim/tests/test_filters.py +33 -0
  152. nautobot/dcim/tests/test_forms.py +51 -2
  153. nautobot/dcim/tests/test_graphql.py +52 -0
  154. nautobot/dcim/tests/test_jobs.py +118 -0
  155. nautobot/dcim/tests/test_models.py +52 -9
  156. nautobot/dcim/tests/test_views.py +30 -84
  157. nautobot/dcim/views.py +13 -28
  158. nautobot/extras/api/customfields.py +2 -2
  159. nautobot/extras/api/serializers.py +123 -85
  160. nautobot/extras/api/views.py +33 -30
  161. nautobot/extras/constants.py +3 -0
  162. nautobot/extras/datasources/git.py +125 -0
  163. nautobot/extras/filters/__init__.py +8 -6
  164. nautobot/extras/forms/base.py +2 -2
  165. nautobot/extras/forms/forms.py +139 -31
  166. nautobot/extras/forms/mixins.py +14 -6
  167. nautobot/extras/group_sync.py +3 -3
  168. nautobot/extras/health_checks.py +1 -2
  169. nautobot/extras/jobs.py +85 -18
  170. nautobot/extras/managers.py +3 -1
  171. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  172. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  173. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  174. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  175. nautobot/extras/models/__init__.py +1 -1
  176. nautobot/extras/models/contacts.py +1 -1
  177. nautobot/extras/models/customfields.py +41 -23
  178. nautobot/extras/models/datasources.py +85 -0
  179. nautobot/extras/models/groups.py +11 -9
  180. nautobot/extras/models/jobs.py +23 -4
  181. nautobot/extras/models/models.py +17 -2
  182. nautobot/extras/models/relationships.py +17 -5
  183. nautobot/extras/plugins/__init__.py +13 -2
  184. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  185. nautobot/extras/plugins/tables.py +16 -14
  186. nautobot/extras/plugins/views.py +65 -69
  187. nautobot/extras/registry.py +1 -1
  188. nautobot/extras/secrets/__init__.py +2 -2
  189. nautobot/extras/signals.py +15 -1
  190. nautobot/extras/tables.py +7 -5
  191. nautobot/extras/templates/extras/computedfield.html +1 -1
  192. nautobot/extras/templates/extras/configcontext.html +1 -1
  193. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  194. nautobot/extras/templates/extras/customfield.html +1 -1
  195. nautobot/extras/templates/extras/customlink.html +1 -1
  196. nautobot/extras/templates/extras/dynamicgroup.html +2 -10
  197. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  198. nautobot/extras/templates/extras/gitrepository.html +1 -1
  199. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  200. nautobot/extras/templates/extras/job_detail.html +17 -1
  201. nautobot/extras/templates/extras/job_edit.html +1 -0
  202. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  203. nautobot/extras/templates/extras/jobhook.html +1 -1
  204. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  205. nautobot/extras/templates/extras/jobresult.html +1 -1
  206. nautobot/extras/templates/extras/marketplace.html +29 -11
  207. nautobot/extras/templates/extras/objectchange.html +1 -1
  208. nautobot/extras/templates/extras/plugin_detail.html +33 -16
  209. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  210. nautobot/extras/templates/extras/relationship.html +1 -63
  211. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  212. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  213. nautobot/extras/templates/extras/secret.html +1 -1
  214. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  215. nautobot/extras/templates/extras/status.html +1 -1
  216. nautobot/extras/templates/extras/tag.html +1 -1
  217. nautobot/extras/templates/extras/webhook.html +1 -1
  218. nautobot/extras/templatetags/job_buttons.py +4 -4
  219. nautobot/extras/test_jobs/api_test_job.py +1 -1
  220. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  221. nautobot/extras/test_jobs/dry_run.py +1 -1
  222. nautobot/extras/test_jobs/fail.py +5 -5
  223. nautobot/extras/test_jobs/file_output.py +1 -1
  224. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  225. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  226. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  227. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  228. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  229. nautobot/extras/test_jobs/log_redaction.py +1 -1
  230. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  231. nautobot/extras/test_jobs/modify_db.py +1 -1
  232. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  233. nautobot/extras/test_jobs/object_var_required.py +1 -1
  234. nautobot/extras/test_jobs/object_vars.py +1 -1
  235. nautobot/extras/test_jobs/pass.py +3 -3
  236. nautobot/extras/test_jobs/profiling.py +1 -1
  237. nautobot/extras/test_jobs/relative_import.py +3 -3
  238. nautobot/extras/test_jobs/singleton.py +16 -0
  239. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  240. nautobot/extras/test_jobs/task_queues.py +1 -1
  241. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  242. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  243. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  244. nautobot/extras/tests/git_helper.py +9 -1
  245. nautobot/extras/tests/integration/__init__.py +29 -16
  246. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  247. nautobot/extras/tests/test_api.py +19 -13
  248. nautobot/extras/tests/test_customfields.py +50 -52
  249. nautobot/extras/tests/test_datasources.py +29 -1
  250. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  251. nautobot/extras/tests/test_filters.py +6 -6
  252. nautobot/extras/tests/test_forms.py +33 -1
  253. nautobot/extras/tests/test_jobs.py +178 -32
  254. nautobot/extras/tests/test_models.py +299 -10
  255. nautobot/extras/tests/test_plugins.py +62 -9
  256. nautobot/extras/tests/test_relationships.py +120 -9
  257. nautobot/extras/tests/test_utils.py +22 -1
  258. nautobot/extras/tests/test_views.py +56 -194
  259. nautobot/extras/utils.py +20 -10
  260. nautobot/extras/views.py +85 -110
  261. nautobot/ipam/api/fields.py +3 -3
  262. nautobot/ipam/api/serializers.py +41 -33
  263. nautobot/ipam/api/views.py +68 -117
  264. nautobot/ipam/factory.py +1 -1
  265. nautobot/ipam/filters.py +3 -2
  266. nautobot/ipam/lookups.py +101 -62
  267. nautobot/ipam/models.py +74 -18
  268. nautobot/ipam/querysets.py +2 -2
  269. nautobot/ipam/tables.py +25 -9
  270. nautobot/ipam/templates/ipam/ipaddress.html +2 -10
  271. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  272. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  273. nautobot/ipam/templates/ipam/prefix.html +2 -10
  274. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  275. nautobot/ipam/templates/ipam/rir.html +1 -1
  276. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  277. nautobot/ipam/templates/ipam/service.html +1 -1
  278. nautobot/ipam/templates/ipam/vlan.html +2 -10
  279. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  280. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  281. nautobot/ipam/templates/ipam/vrf.html +1 -1
  282. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  283. nautobot/ipam/tests/test_api.py +436 -3
  284. nautobot/ipam/tests/test_forms.py +49 -47
  285. nautobot/ipam/tests/test_migrations.py +30 -30
  286. nautobot/ipam/tests/test_models.py +119 -34
  287. nautobot/ipam/tests/test_querysets.py +63 -1
  288. nautobot/ipam/tests/test_utils.py +41 -2
  289. nautobot/ipam/tests/test_views.py +3 -0
  290. nautobot/ipam/utils/__init__.py +54 -17
  291. nautobot/ipam/views.py +61 -87
  292. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  293. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  294. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  295. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  296. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  297. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  298. nautobot/project-static/docs/404.html +131 -14
  299. nautobot/project-static/docs/apps/index.html +131 -14
  300. nautobot/project-static/docs/apps/nautobot-apps.html +132 -16
  301. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  302. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.60a45f97.min.js} +2 -2
  303. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.60a45f97.min.js.map} +2 -2
  304. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  305. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  306. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  307. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +147 -20
  308. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +144 -17
  309. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +459 -132
  310. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +175 -28
  311. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +180 -31
  312. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +138 -16
  313. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +137 -15
  314. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +164 -27
  315. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +187 -38
  316. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +193 -31
  317. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +216 -48
  318. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +324 -75
  319. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +666 -175
  320. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +194 -46
  321. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +538 -177
  322. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +578 -221
  323. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +145 -18
  324. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +156 -25
  325. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +492 -65
  326. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +705 -215
  327. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +943 -422
  328. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +144 -17
  329. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +619 -200
  330. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +474 -159
  331. nautobot/project-static/docs/development/apps/api/configuration-view.html +131 -14
  332. nautobot/project-static/docs/development/apps/api/database-backend-config.html +131 -14
  333. nautobot/project-static/docs/development/apps/api/models/django-admin.html +131 -14
  334. nautobot/project-static/docs/development/apps/api/models/global-search.html +131 -14
  335. nautobot/project-static/docs/development/apps/api/models/graphql.html +131 -14
  336. nautobot/project-static/docs/development/apps/api/models/index.html +131 -14
  337. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +131 -14
  338. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +131 -14
  339. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +131 -14
  340. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +131 -14
  341. nautobot/project-static/docs/development/apps/api/platform-features/index.html +131 -14
  342. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +131 -14
  343. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +131 -14
  344. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +131 -14
  345. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +131 -14
  346. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +153 -17
  347. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +131 -14
  348. nautobot/project-static/docs/development/apps/api/prometheus.html +131 -14
  349. nautobot/project-static/docs/development/apps/api/setup.html +131 -14
  350. nautobot/project-static/docs/development/apps/api/testing.html +131 -14
  351. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +131 -14
  352. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +131 -14
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +131 -14
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +131 -14
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +131 -14
  356. nautobot/project-static/docs/development/apps/api/views/base-template.html +131 -14
  357. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +131 -14
  358. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +131 -14
  359. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +131 -14
  360. nautobot/project-static/docs/development/apps/api/views/index.html +131 -14
  361. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +131 -14
  362. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +131 -14
  363. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +131 -14
  364. nautobot/project-static/docs/development/apps/api/views/notes.html +131 -14
  365. nautobot/project-static/docs/development/apps/api/views/rest-api.html +137 -16
  366. nautobot/project-static/docs/development/apps/api/views/urls.html +131 -14
  367. nautobot/project-static/docs/development/apps/index.html +131 -14
  368. nautobot/project-static/docs/development/apps/migration/code-updates.html +131 -14
  369. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +131 -14
  370. nautobot/project-static/docs/development/apps/migration/from-v1.html +131 -14
  371. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +131 -14
  372. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +131 -14
  373. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +131 -14
  374. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +131 -14
  375. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +135 -18
  376. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +131 -14
  377. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +297 -25
  378. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +131 -14
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +131 -14
  380. nautobot/project-static/docs/development/core/application-registry.html +131 -14
  381. nautobot/project-static/docs/development/core/best-practices.html +131 -14
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +131 -14
  383. nautobot/project-static/docs/development/core/caching.html +131 -14
  384. nautobot/project-static/docs/development/core/controllers.html +131 -14
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +158 -84
  386. nautobot/project-static/docs/development/core/generic-views.html +131 -14
  387. nautobot/project-static/docs/development/core/getting-started.html +334 -234
  388. nautobot/project-static/docs/development/core/homepage.html +134 -17
  389. nautobot/project-static/docs/development/core/index.html +131 -14
  390. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9829 -0
  391. nautobot/project-static/docs/development/core/model-checklist.html +141 -22
  392. nautobot/project-static/docs/development/core/model-features.html +131 -14
  393. nautobot/project-static/docs/development/core/natural-keys.html +131 -14
  394. nautobot/project-static/docs/development/core/navigation-menu.html +131 -14
  395. nautobot/project-static/docs/development/core/release-checklist.html +134 -17
  396. nautobot/project-static/docs/development/core/role-internals.html +131 -14
  397. nautobot/project-static/docs/development/core/settings.html +131 -14
  398. nautobot/project-static/docs/development/core/style-guide.html +134 -17
  399. nautobot/project-static/docs/development/core/templates.html +132 -15
  400. nautobot/project-static/docs/development/core/testing.html +131 -14
  401. nautobot/project-static/docs/development/core/ui-component-framework.html +454 -283
  402. nautobot/project-static/docs/development/core/user-preferences.html +131 -14
  403. nautobot/project-static/docs/development/index.html +131 -14
  404. nautobot/project-static/docs/development/jobs/index.html +301 -132
  405. nautobot/project-static/docs/development/jobs/migration/from-v1.html +131 -14
  406. nautobot/project-static/docs/index.html +139 -33
  407. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  408. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  409. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  410. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  411. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  412. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  413. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  414. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  415. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  416. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  417. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  418. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  419. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  420. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  421. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  422. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  423. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  424. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  425. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  426. nautobot/project-static/docs/objects.inv +0 -0
  427. nautobot/project-static/docs/overview/application_stack.html +132 -17
  428. nautobot/project-static/docs/overview/design_philosophy.html +131 -14
  429. nautobot/project-static/docs/release-notes/index.html +137 -22
  430. nautobot/project-static/docs/release-notes/version-1.0.html +319 -203
  431. nautobot/project-static/docs/release-notes/version-1.1.html +316 -200
  432. nautobot/project-static/docs/release-notes/version-1.2.html +391 -275
  433. nautobot/project-static/docs/release-notes/version-1.3.html +417 -301
  434. nautobot/project-static/docs/release-notes/version-1.4.html +502 -387
  435. nautobot/project-static/docs/release-notes/version-1.5.html +690 -576
  436. nautobot/project-static/docs/release-notes/version-1.6.html +989 -457
  437. nautobot/project-static/docs/release-notes/version-2.0.html +613 -499
  438. nautobot/project-static/docs/release-notes/version-2.1.html +448 -334
  439. nautobot/project-static/docs/release-notes/version-2.2.html +441 -327
  440. nautobot/project-static/docs/release-notes/version-2.3.html +1171 -451
  441. nautobot/project-static/docs/release-notes/version-2.4.html +800 -111
  442. nautobot/project-static/docs/requirements.txt +2 -2
  443. nautobot/project-static/docs/search/search_index.json +1 -1
  444. nautobot/project-static/docs/sitemap.xml +303 -287
  445. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +131 -14
  447. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +131 -14
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +133 -16
  449. nautobot/project-static/docs/user-guide/administration/configuration/index.html +131 -14
  450. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +131 -14
  451. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +195 -18
  452. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +131 -14
  453. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +134 -17
  454. nautobot/project-static/docs/user-guide/administration/guides/docker.html +131 -14
  455. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +131 -14
  456. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +131 -14
  457. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -14
  458. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +131 -14
  459. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +131 -14
  460. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +133 -16
  461. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +131 -14
  462. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +131 -14
  463. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +131 -14
  464. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +151 -18
  465. nautobot/project-static/docs/user-guide/administration/installation/index.html +131 -14
  466. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +132 -15
  467. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +131 -14
  468. nautobot/project-static/docs/user-guide/administration/installation/services.html +131 -14
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +131 -14
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +131 -14
  471. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  472. nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
  473. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +131 -14
  474. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +131 -14
  475. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +131 -14
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +131 -14
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +131 -14
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +131 -14
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +131 -14
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +134 -18
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +131 -14
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +131 -14
  483. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +131 -14
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +131 -14
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +131 -14
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +131 -14
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +131 -14
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +131 -14
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +131 -14
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +131 -14
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +131 -14
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +131 -14
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +131 -14
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +131 -14
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +131 -14
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +131 -14
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +131 -14
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +131 -14
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +131 -14
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +131 -14
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +131 -14
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +131 -14
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +131 -14
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +131 -14
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +131 -14
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +131 -14
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +131 -14
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +131 -14
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +131 -14
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +131 -14
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +131 -14
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +131 -14
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +131 -14
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +131 -14
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +131 -14
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +131 -14
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +131 -14
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +131 -14
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +131 -14
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +131 -14
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +131 -14
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +131 -14
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +131 -14
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +131 -14
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +131 -14
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +131 -14
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +131 -14
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +131 -14
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +131 -14
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +131 -14
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +131 -14
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +131 -14
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +131 -14
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +131 -14
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +131 -14
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +131 -14
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +131 -14
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +135 -22
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +131 -14
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +131 -14
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +131 -14
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +131 -14
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +131 -14
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +131 -14
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +131 -14
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +131 -14
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +131 -14
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +131 -14
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +131 -14
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +131 -14
  551. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +134 -17
  552. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +131 -14
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +131 -14
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +131 -14
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +131 -14
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +131 -14
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +131 -14
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +131 -14
  559. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +131 -14
  560. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +131 -14
  561. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +131 -14
  562. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +131 -14
  563. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +131 -14
  564. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +131 -14
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +131 -14
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +131 -14
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +134 -17
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +131 -14
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +131 -14
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +131 -14
  571. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +131 -14
  572. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +131 -14
  573. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +131 -14
  574. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +236 -34
  575. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +131 -14
  576. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +131 -14
  590. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +131 -14
  591. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +134 -17
  592. nautobot/project-static/docs/{development/core/local-k8s.html → user-guide/feature-guides/wireless-networks-and-controllers.html} +632 -566
  593. nautobot/project-static/docs/user-guide/index.html +131 -14
  594. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +131 -14
  595. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +135 -18
  596. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +131 -14
  597. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +131 -14
  598. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +131 -14
  599. nautobot/project-static/docs/user-guide/platform-functionality/events.html +131 -14
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +131 -14
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +131 -14
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +451 -16
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +131 -14
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +131 -14
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +131 -14
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +135 -17
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +131 -14
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +134 -17
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +131 -14
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +131 -14
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9797 -0
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +131 -14
  613. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +131 -14
  614. nautobot/project-static/docs/user-guide/platform-functionality/note.html +131 -14
  615. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +131 -14
  616. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +132 -15
  617. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +131 -14
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +131 -14
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +179 -35
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +159 -15
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +131 -14
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +131 -14
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +131 -14
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +131 -14
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +131 -14
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +131 -14
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +131 -14
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +131 -14
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +131 -14
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +131 -14
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +131 -14
  632. nautobot/project-static/js/forms.js +1 -1
  633. nautobot/tenancy/api/views.py +9 -13
  634. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  635. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  636. nautobot/tenancy/views.py +4 -2
  637. nautobot/users/admin.py +1 -1
  638. nautobot/users/api/serializers.py +5 -4
  639. nautobot/users/api/views.py +3 -3
  640. nautobot/virtualization/api/serializers.py +4 -4
  641. nautobot/virtualization/api/views.py +5 -24
  642. nautobot/virtualization/filters.py +20 -3
  643. nautobot/virtualization/models.py +1 -1
  644. nautobot/virtualization/tables.py +2 -2
  645. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  646. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  648. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -10
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  651. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  652. nautobot/virtualization/tests/test_filters.py +17 -0
  653. nautobot/wireless/filters.py +2 -2
  654. nautobot/wireless/forms.py +1 -1
  655. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  656. nautobot/wireless/tests/integration/__init__.py +0 -0
  657. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  658. nautobot/wireless/tests/test_filters.py +29 -1
  659. nautobot/wireless/tests/test_views.py +22 -1
  660. nautobot/wireless/views.py +0 -10
  661. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/METADATA +9 -9
  662. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/RECORD +667 -610
  663. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +1 -1
  664. nautobot/core/fixtures/user-data.json +0 -59
  665. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  666. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
  667. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
  668. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
14
14
  SOURCE_DIR = os.path.join(os.path.dirname(__file__), "git_data")
15
15
 
16
16
 
17
- def create_and_populate_git_repository(target_path):
17
+ def create_and_populate_git_repository(target_path, divergent_branch=None):
18
18
  """
19
19
  Create a Git repository in `target_path` and populate it with commits and tags based on contents of `SOURCE_DIR`.
20
20
 
@@ -40,6 +40,9 @@ def create_and_populate_git_repository(target_path):
40
40
  Note that each commit is fully defined by the files in the appropriate subdirectory; if you want a file to exist
41
41
  across multiple separate commits, it must exist in multiple subdirectories. Use of symlinks is encouraged in such
42
42
  a scenario.
43
+
44
+ You can optionally create and check out a divergent branch from the main branch by passing a branch name as the `divergent_branch`.
45
+ This will write a commit to the divergent branch and tag it with the branch name with the `-tag` suffix.
43
46
  """
44
47
  os.makedirs(target_path, exist_ok=True)
45
48
  repo = Repo.init(target_path, initial_branch="main")
@@ -69,6 +72,11 @@ def create_and_populate_git_repository(target_path):
69
72
  # Directory "01-valid-files" --> tag "valid-files" so that we won't break tests if we renumber the directories
70
73
  repo.create_tag(dirname[3:], message=f"Tag based on {dirname} files")
71
74
 
75
+ if divergent_branch:
76
+ repo.create_head(divergent_branch)
77
+ repo.index.commit("divergent-branch")
78
+ repo.create_tag(f"{divergent_branch}-tag", message=f"Tag for divergent branch {divergent_branch}")
79
+
72
80
 
73
81
  if __name__ == "__main__":
74
82
  directory_path = tempfile.TemporaryDirectory().name # pylint: disable=consider-using-with
@@ -6,28 +6,41 @@ from nautobot.dcim.models import Device, DeviceType, Location, LocationType, Man
6
6
  from nautobot.extras.models import Role, Status
7
7
 
8
8
 
9
- def create_test_device():
10
- test_uuid = str(uuid.uuid4())
11
- device_role, _ = Role.objects.get_or_create(name="Device Role")
12
- device_ct = ContentType.objects.get_for_model(Device)
13
- device_role.content_types.add(device_ct)
14
- manufacturer = Manufacturer.objects.create(
15
- name=f"Test Manufacturer {test_uuid}",
16
- )
17
- device_type = DeviceType.objects.create(manufacturer=manufacturer, model=f"Test Model {test_uuid}")
18
- location_type = LocationType.objects.create(name=f"Test Location Type {test_uuid}")
19
- location_type.content_types.add(ContentType.objects.get_for_model(Device))
9
+ def create_test_device(name=None, location_name=None, test_uuid=None):
10
+ if not test_uuid:
11
+ test_uuid = str(uuid.uuid4())
12
+ if not name:
13
+ name = f"Test Device {test_uuid}"
14
+ if not location_name:
15
+ location_name = f"Test Location {test_uuid}"
16
+
17
+ location_type, location_type_created = LocationType.objects.get_or_create(name=f"Test Location Type {test_uuid}")
18
+ if location_type_created:
19
+ location_type.content_types.add(ContentType.objects.get_for_model(Device))
20
+ location_type.save()
21
+
20
22
  location_status = Status.objects.get_for_model(Location).first()
21
- location = Location.objects.create(
22
- name=f"Test Location {test_uuid}",
23
+ location, _ = Location.objects.get_or_create(
24
+ name=location_name,
23
25
  status=location_status,
24
26
  location_type=location_type,
25
27
  )
26
- device = Device.objects.create(
27
- name=f"Test Device {test_uuid}",
28
+
29
+ device_role, device_role_created = Role.objects.get_or_create(name="Device Role")
30
+ if device_role_created:
31
+ device_role.content_types.add(ContentType.objects.get_for_model(Device))
32
+ device_role.save()
33
+
34
+ manufacturer, _ = Manufacturer.objects.get_or_create(
35
+ name=f"Test Manufacturer {test_uuid}",
36
+ )
37
+
38
+ device_type, _ = DeviceType.objects.get_or_create(manufacturer=manufacturer, model=f"Test Model {test_uuid}")
39
+
40
+ return Device.objects.create(
41
+ name=name,
28
42
  role=device_role,
29
43
  device_type=device_type,
30
44
  location=location,
31
45
  status=location_status,
32
46
  )
33
- return device
@@ -4,8 +4,6 @@ from nautobot.core.testing.integration import SeleniumTestCase
4
4
  class PluginBannerTestCase(SeleniumTestCase):
5
5
  """Integration test for rendering of plugin-injected banner content."""
6
6
 
7
- fixtures = ("user-data",)
8
-
9
7
  def test_banner_not_rendered(self):
10
8
  """As implemented, plugin banner does not render if the user is not logged in.
11
9
 
@@ -1207,6 +1207,10 @@ class GitRepositoryTest(APIViewTestCases.APIViewTestCase):
1207
1207
  url = reverse("extras-api:gitrepository-sync", kwargs={"pk": self.repos[0].id})
1208
1208
  response = self.client.post(url, format="json", **self.header)
1209
1209
  self.assertHttpStatus(response, status.HTTP_200_OK)
1210
+ self.assertIn("message", response.data)
1211
+ self.assertIn("job_result", response.data)
1212
+ self.assertEqual(response.data["message"], f"Repository {self.repos[0].name} sync job added to queue.")
1213
+ self.assertIsInstance(response.data["job_result"], dict)
1210
1214
 
1211
1215
  def test_create_with_app_provided_contents(self):
1212
1216
  """Test that `provided_contents` published by an App works."""
@@ -1241,6 +1245,8 @@ class GraphQLQueryTest(APIViewTestCases.APIViewTestCase):
1241
1245
  },
1242
1246
  ]
1243
1247
 
1248
+ choices_fields = ["owner_content_type"]
1249
+
1244
1250
  @classmethod
1245
1251
  def setUpTestData(cls):
1246
1252
  cls.graphqlqueries = (
@@ -1536,10 +1542,10 @@ class JobTest(
1536
1542
  self.assertHttpStatus(response, status.HTTP_404_NOT_FOUND)
1537
1543
 
1538
1544
  # Try post to permitted job
1539
- job_model = Job.objects.get_for_class_path("pass.TestPass")
1545
+ job_model = Job.objects.get_for_class_path("pass.TestPassJob")
1540
1546
  job_model.enabled = True
1541
1547
  job_model.validated_save()
1542
- url = self.get_run_url("pass.TestPass")
1548
+ url = self.get_run_url("pass.TestPassJob")
1543
1549
  response = self.client.post(url, **self.header)
1544
1550
  self.assertHttpStatus(response, self.run_success_response_status)
1545
1551
 
@@ -2129,7 +2135,7 @@ class JobTest(
2129
2135
  @mock.patch("nautobot.extras.api.views.get_worker_count", return_value=1)
2130
2136
  def test_run_job_with_default_queue_with_empty_job_model_job_queues(self, _):
2131
2137
  self.add_permissions("extras.run_job")
2132
- job_model = Job.objects.get_for_class_path("pass.TestPass")
2138
+ job_model = Job.objects.get_for_class_path("pass.TestPassJob")
2133
2139
  data = {
2134
2140
  "task_queue": job_model.default_job_queue.name,
2135
2141
  }
@@ -2137,7 +2143,7 @@ class JobTest(
2137
2143
  job_model.job_queues.set([])
2138
2144
  job_model.enabled = True
2139
2145
  job_model.validated_save()
2140
- url = self.get_run_url("pass.TestPass")
2146
+ url = self.get_run_url("pass.TestPassJob")
2141
2147
  response = self.client.post(url, data, format="json", **self.header)
2142
2148
  self.assertHttpStatus(response, self.run_success_response_status)
2143
2149
 
@@ -2564,10 +2570,10 @@ class ScheduledJobTest(
2564
2570
  @classmethod
2565
2571
  def setUpTestData(cls):
2566
2572
  user = User.objects.create(username="user1", is_active=True)
2567
- job_model = Job.objects.get_for_class_path("pass.TestPass")
2573
+ job_model = Job.objects.get_for_class_path("pass.TestPassJob")
2568
2574
  ScheduledJob.objects.create(
2569
2575
  name="test1",
2570
- task="pass.TestPass",
2576
+ task="pass.TestPassJob",
2571
2577
  job_model=job_model,
2572
2578
  interval=JobExecutionType.TYPE_IMMEDIATELY,
2573
2579
  user=user,
@@ -2576,7 +2582,7 @@ class ScheduledJobTest(
2576
2582
  )
2577
2583
  ScheduledJob.objects.create(
2578
2584
  name="test2",
2579
- task="pass.TestPass",
2585
+ task="pass.TestPassJob",
2580
2586
  job_model=job_model,
2581
2587
  interval=JobExecutionType.TYPE_DAILY,
2582
2588
  user=user,
@@ -2586,7 +2592,7 @@ class ScheduledJobTest(
2586
2592
  )
2587
2593
  ScheduledJob.objects.create(
2588
2594
  name="test3",
2589
- task="pass.TestPass",
2595
+ task="pass.TestPassJob",
2590
2596
  job_model=job_model,
2591
2597
  interval=JobExecutionType.TYPE_CUSTOM,
2592
2598
  crontab="34 12 * * *",
@@ -2601,12 +2607,12 @@ class JobApprovalTest(APITestCase):
2601
2607
  @classmethod
2602
2608
  def setUpTestData(cls):
2603
2609
  cls.additional_user = User.objects.create(username="user1", is_active=True)
2604
- cls.job_model = Job.objects.get_for_class_path("pass.TestPass")
2610
+ cls.job_model = Job.objects.get_for_class_path("pass.TestPassJob")
2605
2611
  cls.job_model.enabled = True
2606
2612
  cls.job_model.save()
2607
2613
  cls.scheduled_job = ScheduledJob.objects.create(
2608
2614
  name="test pass",
2609
- task="pass.TestPass",
2615
+ task="pass.TestPassJob",
2610
2616
  job_model=cls.job_model,
2611
2617
  interval=JobExecutionType.TYPE_IMMEDIATELY,
2612
2618
  user=cls.additional_user,
@@ -2659,7 +2665,7 @@ class JobApprovalTest(APITestCase):
2659
2665
  self.add_permissions("extras.approve_job", "extras.view_scheduledjob", "extras.change_scheduledjob")
2660
2666
  scheduled_job = ScheduledJob.objects.create(
2661
2667
  name="test",
2662
- task="pass.TestPass",
2668
+ task="pass.TestPassJob",
2663
2669
  job_model=self.job_model,
2664
2670
  interval=JobExecutionType.TYPE_IMMEDIATELY,
2665
2671
  user=self.user,
@@ -2682,7 +2688,7 @@ class JobApprovalTest(APITestCase):
2682
2688
  self.add_permissions("extras.approve_job", "extras.view_scheduledjob", "extras.change_scheduledjob")
2683
2689
  scheduled_job = ScheduledJob.objects.create(
2684
2690
  name="test",
2685
- task="pass.TestPass",
2691
+ task="pass.TestPassJob",
2686
2692
  job_model=self.job_model,
2687
2693
  interval=JobExecutionType.TYPE_FUTURE,
2688
2694
  one_off=True,
@@ -2699,7 +2705,7 @@ class JobApprovalTest(APITestCase):
2699
2705
  self.add_permissions("extras.approve_job", "extras.view_scheduledjob", "extras.change_scheduledjob")
2700
2706
  scheduled_job = ScheduledJob.objects.create(
2701
2707
  name="test",
2702
- task="pass.TestPass",
2708
+ task="pass.TestPassJob",
2703
2709
  job_model=self.job_model,
2704
2710
  interval=JobExecutionType.TYPE_FUTURE,
2705
2711
  one_off=True,
@@ -53,12 +53,12 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
53
53
 
54
54
  instance.refresh_from_db()
55
55
  instance.key = "custom_field_2"
56
- with self.assertRaises(ValidationError):
56
+ with self.assertRaisesRegex(ValidationError, "Key cannot be changed once created"):
57
57
  instance.validated_save()
58
58
 
59
59
  instance.refresh_from_db()
60
60
  instance.type = CustomFieldTypeChoices.TYPE_SELECT
61
- with self.assertRaises(ValidationError):
61
+ with self.assertRaisesRegex(ValidationError, "Type cannot be changed once created"):
62
62
  instance.validated_save()
63
63
 
64
64
  def test_simple_fields(self):
@@ -165,9 +165,14 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
165
165
  cf.save()
166
166
  cf.content_types.set([obj_type])
167
167
 
168
- CustomFieldChoice.objects.create(custom_field=cf, value="Option A")
169
- CustomFieldChoice.objects.create(custom_field=cf, value="Option B")
170
- CustomFieldChoice.objects.create(custom_field=cf, value="Option C")
168
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option A", weight=100)
169
+ self.assertEqual(["Option A"], cf.choices)
170
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option B", weight=200)
171
+ self.assertEqual(["Option A", "Option B"], cf.choices)
172
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option C", weight=300)
173
+ self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
174
+ with self.assertNumQueries(0): # verify caching
175
+ self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
171
176
 
172
177
  # Assign a value to the first Location
173
178
  location = Location.objects.get(name="Location A")
@@ -199,9 +204,14 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
199
204
  cf.save()
200
205
  cf.content_types.set([obj_type])
201
206
 
202
- CustomFieldChoice.objects.create(custom_field=cf, value="Option A")
203
- CustomFieldChoice.objects.create(custom_field=cf, value="Option B")
204
- CustomFieldChoice.objects.create(custom_field=cf, value="Option C")
207
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option A", weight=100)
208
+ self.assertEqual(["Option A"], cf.choices)
209
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option B", weight=200)
210
+ self.assertEqual(["Option A", "Option B"], cf.choices)
211
+ CustomFieldChoice.objects.create(custom_field=cf, value="Option C", weight=300)
212
+ self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
213
+ with self.assertNumQueries(0): # verify caching
214
+ self.assertEqual(["Option A", "Option B", "Option C"], cf.choices)
205
215
 
206
216
  # Assign a value to the first Location
207
217
  location = Location.objects.get(name="Location A")
@@ -293,21 +303,18 @@ class CustomFieldTest(ModelTestCases.BaseModelTestCase, TestCase):
293
303
  # Assign a disallowed value (list) to the first Location
294
304
  location = Location.objects.get(name="Location A")
295
305
  location.cf[cf.key] = ["I", "am", "a", "list"]
296
- with self.assertRaises(ValidationError) as context:
306
+ with self.assertRaisesRegex(ValidationError, "Value must be a string"):
297
307
  location.validated_save()
298
- self.assertIn("Value must be a string", str(context.exception))
299
308
 
300
309
  # Assign another disallowed value (int) to the first Location
301
310
  location.cf[cf.key] = 2
302
- with self.assertRaises(ValidationError) as context:
311
+ with self.assertRaisesRegex(ValidationError, "Value must be a string"):
303
312
  location.validated_save()
304
- self.assertIn("Value must be a string", str(context.exception))
305
313
 
306
314
  # Assign another disallowed value (bool) to the first Location
307
315
  location.cf[cf.key] = True
308
- with self.assertRaises(ValidationError) as context:
316
+ with self.assertRaisesRegex(ValidationError, "Value must be a string"):
309
317
  location.validated_save()
310
- self.assertIn("Value must be a string", str(context.exception))
311
318
 
312
319
  # Delete the stored value
313
320
  location.cf.pop(cf.key)
@@ -1294,7 +1301,7 @@ class CustomFieldModelTest(TestCase):
1294
1301
  custom_field.content_types.set([ContentType.objects.get_for_model(Provider)])
1295
1302
 
1296
1303
  provider = Provider.objects.create(name="Test")
1297
- with self.assertRaises(ValidationError):
1304
+ with self.assertRaisesRegex(ValidationError, "Missing required custom field 'custom_field'"):
1298
1305
  provider.validated_save()
1299
1306
 
1300
1307
  def test_custom_field_required_on_update(self):
@@ -1312,7 +1319,7 @@ class CustomFieldModelTest(TestCase):
1312
1319
  provider = Provider.objects.create(name="Test", _custom_field_data={"custom_field": "Value"})
1313
1320
  provider.validated_save()
1314
1321
  provider._custom_field_data.pop("custom_field")
1315
- with self.assertRaises(ValidationError):
1322
+ with self.assertRaisesRegex(ValidationError, "Missing required custom field 'custom_field'"):
1316
1323
  provider.validated_save()
1317
1324
 
1318
1325
  def test_update_removed_custom_field(self):
@@ -1373,7 +1380,7 @@ class CustomFieldModelTest(TestCase):
1373
1380
  """
1374
1381
  Check that a ValidationError is raised if any required custom fields are not present.
1375
1382
  """
1376
- cf3 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Baz", required=True)
1383
+ cf3 = CustomField(key="baz", type=CustomFieldTypeChoices.TYPE_TEXT, label="Baz", required=True)
1377
1384
  cf3.save()
1378
1385
  cf3.content_types.set([ContentType.objects.get_for_model(Location)])
1379
1386
 
@@ -1381,7 +1388,7 @@ class CustomFieldModelTest(TestCase):
1381
1388
 
1382
1389
  # Set custom field data with a required field omitted
1383
1390
  location.cf["foo"] = "abc"
1384
- with self.assertRaises(ValidationError):
1391
+ with self.assertRaisesRegex(ValidationError, "Missing required custom field 'baz'"):
1385
1392
  location.clean()
1386
1393
 
1387
1394
  location.cf["baz"] = "def"
@@ -1427,38 +1434,20 @@ class CustomFieldModelTest(TestCase):
1427
1434
  """
1428
1435
  Check the GraphQL validation method on CustomField Key Attribute.
1429
1436
  """
1430
- # Check if it catches the cf.key starting with a digit.
1431
- cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Test 1", key="12_test_1")
1432
- with self.assertRaises(ValidationError) as error:
1433
- cf1.validated_save()
1434
- self.assertIn(
1435
- "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
1436
- str(error.exception),
1437
- )
1438
- # Check if it catches the cf.key with whitespace.
1439
- cf1.key = "test 1"
1440
- with self.assertRaises(ValidationError) as error:
1441
- cf1.validated_save()
1442
- self.assertIn(
1443
- "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
1444
- str(error.exception),
1445
- )
1446
- # Check if it catches the cf.key with hyphens.
1447
- cf1.key = "test-1-custom-field"
1448
- with self.assertRaises(ValidationError) as error:
1449
- cf1.validated_save()
1450
- self.assertIn(
1451
- "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
1452
- str(error.exception),
1453
- )
1454
- # Check if it catches the cf.key with special characters
1455
- cf1.key = "test_1_custom_f)(&d"
1456
- with self.assertRaises(ValidationError) as error:
1457
- cf1.validated_save()
1458
- self.assertIn(
1459
- "This key is not Python/GraphQL safe. Please do not start the key with a digit and do not use hyphens or whitespace",
1460
- str(error.exception),
1461
- )
1437
+ cf1 = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, label="Test 1")
1438
+ for invalid_key in [
1439
+ "12_test_1", # Check if it catches the cf.key starting with a digit.
1440
+ "test 1", # Check if it catches the cf.key with whitespace.
1441
+ "test-1-custom-field", # Check if it catches the cf.key with hyphens.
1442
+ "test_1_custom_f)(&d", # Check if it catches the cf.key with special characters
1443
+ ]:
1444
+ with self.assertRaisesRegex(
1445
+ ValidationError,
1446
+ "This key is not Python/GraphQL safe. "
1447
+ "Please do not start the key with a digit and do not use hyphens or whitespace",
1448
+ ):
1449
+ cf1.key = invalid_key
1450
+ cf1.validated_save()
1462
1451
 
1463
1452
 
1464
1453
  class CustomFieldFilterTest(TestCase):
@@ -1934,9 +1923,11 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
1934
1923
  )
1935
1924
  self.cf.save()
1936
1925
  self.cf.content_types.set([obj_type])
1926
+ self.assertEqual(self.cf.choices, [])
1937
1927
 
1938
1928
  self.choice = CustomFieldChoice(custom_field=self.cf, value="Foo")
1939
1929
  self.choice.save()
1930
+ self.assertEqual(self.cf.choices, ["Foo"])
1940
1931
 
1941
1932
  location_status = Status.objects.get_for_model(Location).first()
1942
1933
  self.location_type = LocationType.objects.get(name="Campus")
@@ -1952,7 +1943,7 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
1952
1943
 
1953
1944
  def test_default_value_must_be_valid_choice_sad_path(self):
1954
1945
  self.cf.default = "invalid value"
1955
- with self.assertRaises(ValidationError):
1946
+ with self.assertRaisesRegex(ValidationError, 'Invalid default value "invalid value"'):
1956
1947
  self.cf.full_clean()
1957
1948
 
1958
1949
  def test_default_value_must_be_valid_choice_happy_path(self):
@@ -1965,6 +1956,13 @@ class CustomFieldChoiceTest(ModelTestCases.BaseModelTestCase):
1965
1956
  with self.assertRaises(ProtectedError):
1966
1957
  self.choice.delete()
1967
1958
 
1959
+ def test_inactive_choice_can_be_deleted(self):
1960
+ self.location._custom_field_data.pop("cf1")
1961
+ self.location.validated_save()
1962
+ self.assertEqual(self.cf.choices, ["Foo"])
1963
+ self.choice.delete()
1964
+ self.assertEqual(self.cf.choices, [])
1965
+
1968
1966
  def test_custom_choice_deleted_with_field(self):
1969
1967
  self.cf.delete()
1970
1968
  self.assertEqual(CustomField.objects.count(), 1) # custom field automatically added by the Example App
@@ -2240,7 +2238,7 @@ class CustomFieldTableTest(TestCase):
2240
2238
  self.assertIsNotNone(custom_column, internal_col_name)
2241
2239
  self.assertIsInstance(custom_column, CustomFieldColumn)
2242
2240
 
2243
- rendered_value = bound_row.get_cell(internal_col_name)
2241
+ rendered_value = bound_row.get_cell(internal_col_name) # pylint: disable=no-member
2244
2242
  self.assertEqual(rendered_value, col_expected_value)
2245
2243
 
2246
2244
 
@@ -30,6 +30,7 @@ from nautobot.extras.models import (
30
30
  ConfigContextSchema,
31
31
  ExportTemplate,
32
32
  GitRepository,
33
+ GraphQLQuery,
33
34
  Job,
34
35
  JobButton,
35
36
  JobHook,
@@ -197,6 +198,15 @@ class GitTest(TransactionTestCase):
197
198
  )
198
199
  self.assertIsNotNone(export_template_vlan)
199
200
 
201
+ def assert_graphql_query_exists(self, name="device_names.gql"):
202
+ """Helper function to assert Graphql query exists."""
203
+ graphql_query = GraphQLQuery.objects.get(
204
+ name=name,
205
+ owner_object_id=self.repo.pk,
206
+ owner_content_type=ContentType.objects.get_for_model(GitRepository),
207
+ )
208
+ self.assertIsNotNone(graphql_query)
209
+
200
210
  def assert_job_exists(self, name="MyJob", installed=True):
201
211
  """Helper function to assert JobModel and registered Job exist."""
202
212
  # Is it registered correctly in the database?
@@ -285,7 +295,8 @@ class GitTest(TransactionTestCase):
285
295
 
286
296
  self.repo.secrets_group = secrets_group
287
297
  self.repo.remote_url = "http://localhost/git.git"
288
- self.repo.provided_contents.remove("extras.job") # avoid failing due to lack of jobs module
298
+ # avoid failing due to lack of jobs module
299
+ self.repo.provided_contents.remove("extras.job") # pylint: disable=no-member
289
300
  self.repo.save()
290
301
 
291
302
  self.mock_request.id = uuid.uuid4()
@@ -347,6 +358,10 @@ class GitTest(TransactionTestCase):
347
358
  # Case when ContentType.model != ContentType.name, template was added and deleted during sync (#570)
348
359
  self.assert_export_template_vlan_exists("template.j2")
349
360
 
361
+ # Make sure Graphgl queries were loaded
362
+ self.assert_graphql_query_exists("device_names")
363
+ self.assert_graphql_query_exists("device_interfaces")
364
+
350
365
  # Make sure Jobs were successfully loaded from file and registered as JobModels
351
366
  self.assert_job_exists(name="MyJob")
352
367
  self.assert_job_exists(name="MyJobButtonReceiver")
@@ -438,6 +453,7 @@ class GitTest(TransactionTestCase):
438
453
  self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.id).exists())
439
454
  self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.id).exists())
440
455
  self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.id).exists())
456
+ self.assertFalse(GraphQLQuery.objects.filter(owner_object_id=self.repo.id).exists())
441
457
  self.assertFalse(Job.objects.filter(module_name__startswith=f"{self.repo.slug}.").exists())
442
458
  device = Device.objects.get(name=self.device.name)
443
459
  self.assertIsNone(device.local_config_context_data)
@@ -504,6 +520,11 @@ class GitTest(TransactionTestCase):
504
520
  grouping="jobs",
505
521
  message__contains="Error in loading Jobs from Git repository: ",
506
522
  )
523
+ failure_logs.get(
524
+ grouping="graphql queries",
525
+ message__contains="Error processing GraphQL query file 'bad_device_names.gql': Syntax Error GraphQL (4:5) Expected Name, found }",
526
+ )
527
+
507
528
  except (AssertionError, JobLogEntry.DoesNotExist):
508
529
  for log in log_entries:
509
530
  print(log.message)
@@ -629,6 +650,8 @@ class GitTest(TransactionTestCase):
629
650
  self.assert_export_template_device("template.j2")
630
651
  self.assert_export_template_html_exist("template2.html")
631
652
  self.assert_export_template_vlan_exists("template.j2")
653
+ self.assert_graphql_query_exists(name="device_names")
654
+ self.assert_graphql_query_exists(name="device_interfaces")
632
655
  self.assert_job_exists(name="MyJob")
633
656
  self.assert_job_exists(name="MyJobButtonReceiver")
634
657
  self.assert_job_exists(name="MyJobHookReceiver")
@@ -668,6 +691,8 @@ class GitTest(TransactionTestCase):
668
691
  self.assert_export_template_device("template.j2")
669
692
  self.assert_export_template_html_exist("template2.html")
670
693
  self.assert_export_template_vlan_exists("template.j2")
694
+ self.assert_graphql_query_exists("device_names")
695
+ self.assert_graphql_query_exists("device_interfaces")
671
696
  self.assert_job_exists(name="MyJob")
672
697
  self.assert_job_exists(name="MyJobButtonReceiver")
673
698
  self.assert_job_exists(name="MyJobHookReceiver")
@@ -710,6 +735,8 @@ class GitTest(TransactionTestCase):
710
735
  log_entries.get(message__contains="Addition - `export_templates/dcim/device/template.j2`")
711
736
  log_entries.get(message__contains="Addition - `export_templates/dcim/device/template2.html`")
712
737
  log_entries.get(message__contains="Addition - `export_templates/ipam/vlan/template.j2`")
738
+ log_entries.get(message__contains="Addition - `graphql_queries/device_interfaces.gql`")
739
+ log_entries.get(message__contains="Addition - `graphql_queries/device_names.gql`")
713
740
  log_entries.get(message__contains="Addition - `jobs/__init__.py`")
714
741
  log_entries.get(message__contains="Addition - `jobs/my_job.py`")
715
742
  except JobLogEntry.DoesNotExist:
@@ -720,6 +747,7 @@ class GitTest(TransactionTestCase):
720
747
  self.assertFalse(ConfigContextSchema.objects.filter(owner_object_id=self.repo.pk).exists())
721
748
  self.assertFalse(ConfigContext.objects.filter(owner_object_id=self.repo.pk).exists())
722
749
  self.assertFalse(ExportTemplate.objects.filter(owner_object_id=self.repo.pk).exists())
750
+ self.assertFalse(GraphQLQuery.objects.filter(owner_object_id=self.repo.pk).exists())
723
751
  self.assertFalse(Job.objects.filter(module_name__startswith=self.repo.slug).exists())
724
752
 
725
753
  # TODO: test dry-run against a branch name
@@ -587,7 +587,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
587
587
  """
588
588
  pfx_content_type = ContentType.objects.get_for_model(Prefix)
589
589
  group = DynamicGroup(name="pfx", content_type=pfx_content_type)
590
- filterset = group.filterset_class()
590
+ filterset = group.filterset_class() # pylint: disable=not-callable # should not be None here!
591
591
  fields = group._map_filter_fields
592
592
 
593
593
  # We know that `within_include` does not have a `generate_query_{filter_method}` method.
@@ -890,11 +890,11 @@ class JobFilterSetTestCase(FilterTestCases.FilterTestCase):
890
890
  Job.objects.last().tags.set(Tag.objects.get_for_model(Job)[:3])
891
891
 
892
892
  def test_installed(self):
893
- params = {"job_class_name": "TestPass", "installed": True}
893
+ params = {"job_class_name": "TestPassJob", "installed": True}
894
894
  self.assertTrue(self.filterset(params, self.queryset).qs.exists())
895
895
 
896
896
  def test_enabled(self):
897
- params = {"job_class_name": "TestPass", "enabled": False}
897
+ params = {"job_class_name": "TestPassJob", "enabled": False}
898
898
  self.assertTrue(self.filterset(params, self.queryset).qs.exists())
899
899
 
900
900
  def test_dryrun_default(self):
@@ -992,11 +992,11 @@ class JobResultFilterSetTestCase(FilterTestCases.FilterTestCase):
992
992
  jobs = Job.objects.all()[:3]
993
993
  cls.jobs = jobs
994
994
  user = User.objects.create(username="user1", is_active=True)
995
- job_model = Job.objects.get_for_class_path("pass.TestPass")
995
+ job_model = Job.objects.get_for_class_path("pass.TestPassJob")
996
996
  scheduled_jobs = [
997
997
  ScheduledJob.objects.create(
998
998
  name="test1",
999
- task="pass.TestPass",
999
+ task="pass.TestPassJob",
1000
1000
  job_model=job_model,
1001
1001
  interval=JobExecutionType.TYPE_IMMEDIATELY,
1002
1002
  user=user,
@@ -1005,7 +1005,7 @@ class JobResultFilterSetTestCase(FilterTestCases.FilterTestCase):
1005
1005
  ),
1006
1006
  ScheduledJob.objects.create(
1007
1007
  name="test2",
1008
- task="pass.TestPass",
1008
+ task="pass.TestPassJob",
1009
1009
  job_model=job_model,
1010
1010
  interval=JobExecutionType.TYPE_DAILY,
1011
1011
  user=user,
@@ -1015,7 +1015,7 @@ class JobResultFilterSetTestCase(FilterTestCases.FilterTestCase):
1015
1015
  ),
1016
1016
  ScheduledJob.objects.create(
1017
1017
  name="test3",
1018
- task="pass.TestPass",
1018
+ task="pass.TestPassJob",
1019
1019
  job_model=job_model,
1020
1020
  interval=JobExecutionType.TYPE_CUSTOM,
1021
1021
  crontab="34 12 * * *",
@@ -4,12 +4,13 @@ import warnings
4
4
  from django.contrib.auth import get_user_model
5
5
  from django.contrib.contenttypes.models import ContentType
6
6
  from django.db.models import Q
7
+ from django.forms import ChoiceField, MultipleChoiceField
7
8
  from django.test import override_settings, TestCase
8
9
 
9
10
  from nautobot.dcim.forms import DeviceForm, LocationBulkEditForm, LocationForm
10
11
  import nautobot.dcim.models as dcim_models
11
12
  from nautobot.dcim.models import Device, Location, LocationType
12
- from nautobot.extras.choices import RelationshipTypeChoices
13
+ from nautobot.extras.choices import CustomFieldTypeChoices, RelationshipTypeChoices
13
14
  from nautobot.extras.forms import (
14
15
  ConfigContextFilterForm,
15
16
  ConfigContextForm,
@@ -25,6 +26,7 @@ from nautobot.extras.forms import (
25
26
  WebhookForm,
26
27
  )
27
28
  from nautobot.extras.models import (
29
+ CustomField,
28
30
  Job,
29
31
  JobButton,
30
32
  JobHook,
@@ -908,6 +910,19 @@ class RelationshipModelBulkEditFormMixinTestCase(TestCase):
908
910
  ras = RelationshipAssociation.objects.filter(relationship=self.rel_mtom_s)
909
911
  self.assertEqual(1, ras.count())
910
912
 
913
+ def test_form_save_relationship_with_nullified_fields_is_none(self):
914
+ """Test save_relationships with nullified_fields=None."""
915
+ form = LocationBulkEditForm(
916
+ model=dcim_models.Location,
917
+ data={
918
+ "pks": [self.locations[0].pk],
919
+ "add_cr_multiplexing__destination": [ipaddress.pk for ipaddress in self.ipaddresses],
920
+ "add_cr_peer_locations__peer": [self.locations[1].pk],
921
+ },
922
+ )
923
+ form.is_valid()
924
+ form.save_relationships(instance=self.locations[0], nullified_fields=None)
925
+
911
926
  def test_location_form_remove_mtom(self):
912
927
  """Test removal of relationship-associations for many-to-many relationships."""
913
928
  RelationshipAssociation.objects.create(
@@ -1217,3 +1232,20 @@ class CustomFieldModelFormMixinTestCase(TestCase):
1217
1232
 
1218
1233
  custom_field_form = TestForm()
1219
1234
  self.assertIn("_custom_field_data", custom_field_form.fields)
1235
+
1236
+
1237
+ class CustomFieldTestCase(TestCase):
1238
+ def test_to_form_field_type_select(self):
1239
+ """Verify that `to_form_field` and `to_filter_form_field` return the correct field types for a select-type CustomField."""
1240
+ custom_field = CustomField.objects.create(
1241
+ type=CustomFieldTypeChoices.TYPE_SELECT,
1242
+ label="Custom Field Select",
1243
+ )
1244
+ form = custom_field.to_filter_form_field()
1245
+ self.assertIsInstance(form, MultipleChoiceField)
1246
+
1247
+ form = custom_field.to_form_field(for_filter_form=True)
1248
+ self.assertIsInstance(form, MultipleChoiceField)
1249
+
1250
+ form = custom_field.to_form_field(for_filter_form=False)
1251
+ self.assertIsInstance(form, ChoiceField)