nautobot 2.2.8__py3-none-any.whl → 2.3.0__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 (704) hide show
  1. nautobot/apps/forms.py +4 -0
  2. nautobot/apps/models.py +10 -1
  3. nautobot/circuits/__init__.py +0 -1
  4. nautobot/circuits/apps.py +1 -0
  5. nautobot/circuits/factory.py +15 -3
  6. nautobot/circuits/filters.py +13 -0
  7. nautobot/circuits/forms.py +13 -0
  8. nautobot/circuits/migrations/0021_alter_circuit_status_alter_circuittermination__path.py +32 -0
  9. nautobot/circuits/migrations/0022_circuittermination_cloud_network.py +25 -0
  10. nautobot/circuits/models.py +16 -3
  11. nautobot/circuits/tables.py +16 -2
  12. nautobot/circuits/templates/circuits/circuittermination_create.html +10 -2
  13. nautobot/circuits/templates/circuits/circuittermination_retrieve.html +6 -0
  14. nautobot/circuits/templates/circuits/inc/circuit_termination.html +6 -1
  15. nautobot/circuits/tests/test_api.py +7 -5
  16. nautobot/circuits/tests/test_filters.py +12 -5
  17. nautobot/circuits/tests/test_models.py +33 -2
  18. nautobot/circuits/views.py +2 -3
  19. nautobot/cloud/__init__.py +0 -0
  20. nautobot/cloud/api/__init__.py +0 -0
  21. nautobot/cloud/api/serializers.py +54 -0
  22. nautobot/cloud/api/urls.py +16 -0
  23. nautobot/cloud/api/views.py +48 -0
  24. nautobot/cloud/apps.py +13 -0
  25. nautobot/cloud/factory.py +113 -0
  26. nautobot/cloud/filters.py +187 -0
  27. nautobot/cloud/forms.py +339 -0
  28. nautobot/cloud/homepage.py +43 -0
  29. nautobot/cloud/migrations/0001_initial.py +304 -0
  30. nautobot/cloud/migrations/__init__.py +0 -0
  31. nautobot/cloud/models.py +246 -0
  32. nautobot/cloud/navigation.py +85 -0
  33. nautobot/cloud/tables.py +157 -0
  34. nautobot/cloud/templates/cloud/cloudaccount_retrieve.html +43 -0
  35. nautobot/cloud/templates/cloud/cloudnetwork_retrieve.html +122 -0
  36. nautobot/cloud/templates/cloud/cloudnetwork_update.html +33 -0
  37. nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +111 -0
  38. nautobot/cloud/templates/cloud/cloudservice_retrieve.html +69 -0
  39. nautobot/cloud/templates/cloud/cloudservice_update.html +25 -0
  40. nautobot/cloud/tests/__init__.py +0 -0
  41. nautobot/cloud/tests/test_api.py +248 -0
  42. nautobot/cloud/tests/test_filters.py +125 -0
  43. nautobot/cloud/tests/test_models.py +43 -0
  44. nautobot/cloud/tests/test_views.py +153 -0
  45. nautobot/cloud/urls.py +14 -0
  46. nautobot/cloud/views.py +181 -0
  47. nautobot/core/__init__.py +0 -3
  48. nautobot/core/api/metadata.py +1 -0
  49. nautobot/core/api/parsers.py +7 -1
  50. nautobot/core/api/urls.py +1 -0
  51. nautobot/core/api/utils.py +1 -0
  52. nautobot/core/api/views.py +4 -0
  53. nautobot/core/apps/__init__.py +6 -3
  54. nautobot/core/constants.py +8 -0
  55. nautobot/core/factory.py +32 -1
  56. nautobot/core/filters.py +110 -14
  57. nautobot/core/forms/fields.py +10 -4
  58. nautobot/core/forms/forms.py +11 -3
  59. nautobot/core/forms/widgets.py +18 -1
  60. nautobot/core/graphql/generators.py +2 -2
  61. nautobot/core/graphql/schema.py +28 -7
  62. nautobot/core/jobs/__init__.py +20 -3
  63. nautobot/core/jobs/cleanup.py +100 -0
  64. nautobot/core/jobs/groups.py +38 -0
  65. nautobot/core/management/commands/generate_test_data.py +116 -3
  66. nautobot/core/models/__init__.py +34 -9
  67. nautobot/core/models/generics.py +19 -3
  68. nautobot/core/models/name_color_content_types.py +7 -28
  69. nautobot/core/models/querysets.py +4 -3
  70. nautobot/core/models/tree_queries.py +1 -1
  71. nautobot/core/models/utils.py +21 -5
  72. nautobot/core/settings.py +15 -19
  73. nautobot/core/settings.yaml +48 -13
  74. nautobot/core/settings_funcs.py +103 -0
  75. nautobot/core/tables.py +130 -56
  76. nautobot/core/templates/admin/search_form.html +1 -1
  77. nautobot/core/templates/buttons/add.html +11 -3
  78. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  80. nautobot/core/templates/buttons/export.html +101 -53
  81. nautobot/core/templates/buttons/job_import.html +11 -3
  82. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  83. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  84. nautobot/core/templates/generic/object_changelog.html +0 -9
  85. nautobot/core/templates/generic/object_list.html +156 -17
  86. nautobot/core/templates/generic/object_retrieve.html +80 -16
  87. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  88. nautobot/core/templates/inc/javascript.html +2 -0
  89. nautobot/core/templates/inc/media.html +2 -2
  90. nautobot/core/templates/inc/nav_menu.html +1 -0
  91. nautobot/core/templates/inc/paginator.html +7 -7
  92. nautobot/core/templates/inc/search_panel.html +2 -2
  93. nautobot/core/templates/inc/table.html +2 -2
  94. nautobot/core/templates/nautobot_config.py.j2 +28 -8
  95. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  96. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  97. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  98. nautobot/core/templates/utilities/theme_preview.html +25 -8
  99. nautobot/core/templates/utilities/worker_status.html +152 -0
  100. nautobot/core/templatetags/buttons.py +335 -38
  101. nautobot/core/templatetags/form_helpers.py +1 -1
  102. nautobot/core/templatetags/helpers.py +181 -11
  103. nautobot/core/testing/api.py +5 -4
  104. nautobot/core/testing/filters.py +63 -14
  105. nautobot/core/testing/mixins.py +46 -0
  106. nautobot/core/testing/models.py +22 -0
  107. nautobot/core/testing/schema.py +4 -8
  108. nautobot/core/testing/views.py +31 -14
  109. nautobot/core/tests/integration/test_general_functionality.py +1 -1
  110. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  111. nautobot/core/tests/integration/test_swagger.py +1 -1
  112. nautobot/core/tests/nautobot_config.py +0 -1
  113. nautobot/core/tests/runner.py +2 -2
  114. nautobot/core/tests/test_api.py +1 -0
  115. nautobot/core/tests/test_authentication.py +7 -2
  116. nautobot/core/tests/test_filters.py +11 -9
  117. nautobot/core/tests/test_forms.py +9 -0
  118. nautobot/core/tests/test_graphql.py +27 -16
  119. nautobot/core/tests/test_jobs.py +204 -2
  120. nautobot/core/tests/test_tables.py +3 -1
  121. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  122. nautobot/core/tests/test_templatetags_netutils.py +3 -3
  123. nautobot/core/tests/test_utils.py +31 -20
  124. nautobot/core/tests/test_views.py +6 -6
  125. nautobot/core/urls.py +8 -3
  126. nautobot/core/utils/deprecation.py +29 -0
  127. nautobot/core/utils/filtering.py +12 -9
  128. nautobot/core/utils/lookup.py +37 -2
  129. nautobot/core/utils/requests.py +4 -1
  130. nautobot/core/views/__init__.py +137 -24
  131. nautobot/core/views/generic.py +119 -67
  132. nautobot/core/views/mixins.py +105 -36
  133. nautobot/core/views/paginator.py +9 -3
  134. nautobot/core/views/renderers.py +121 -56
  135. nautobot/core/views/utils.py +81 -1
  136. nautobot/dcim/__init__.py +0 -1
  137. nautobot/dcim/api/serializers.py +180 -44
  138. nautobot/dcim/api/urls.py +7 -3
  139. nautobot/dcim/api/views.py +53 -7
  140. nautobot/dcim/apps.py +3 -0
  141. nautobot/dcim/choices.py +25 -0
  142. nautobot/dcim/constants.py +7 -0
  143. nautobot/dcim/factory.py +252 -18
  144. nautobot/dcim/filters/__init__.py +373 -193
  145. nautobot/dcim/filters/mixins.py +274 -1
  146. nautobot/dcim/forms.py +834 -121
  147. nautobot/dcim/graphql/types.py +2 -2
  148. nautobot/dcim/homepage.py +1 -1
  149. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  150. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  151. nautobot/dcim/migrations/0061_module_models.py +862 -0
  152. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  153. nautobot/dcim/models/__init__.py +8 -0
  154. nautobot/dcim/models/cables.py +15 -0
  155. nautobot/dcim/models/device_component_templates.py +207 -53
  156. nautobot/dcim/models/device_components.py +282 -99
  157. nautobot/dcim/models/devices.py +472 -13
  158. nautobot/dcim/models/racks.py +0 -1
  159. nautobot/dcim/navigation.py +47 -0
  160. nautobot/dcim/signals.py +3 -3
  161. nautobot/dcim/tables/__init__.py +35 -23
  162. nautobot/dcim/tables/devices.py +248 -47
  163. nautobot/dcim/tables/devicetypes.py +65 -9
  164. nautobot/dcim/tables/racks.py +5 -1
  165. nautobot/dcim/tables/template_code.py +46 -26
  166. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  167. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  168. nautobot/dcim/templates/dcim/device/base.html +14 -6
  169. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  170. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  171. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  172. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  173. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  174. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  175. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  176. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  177. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  178. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  179. nautobot/dcim/templates/dcim/device.html +45 -1
  180. nautobot/dcim/templates/dcim/device_component.html +13 -5
  181. nautobot/dcim/templates/dcim/device_list.html +2 -1
  182. nautobot/dcim/templates/dcim/deviceredundancygroup_retrieve.html +6 -0
  183. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  184. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  185. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  186. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  187. nautobot/dcim/templates/dcim/interface.html +17 -2
  188. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  189. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  190. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  191. nautobot/dcim/templates/dcim/module/base.html +97 -0
  192. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  193. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  194. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  195. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  196. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  197. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  198. nautobot/dcim/templates/dcim/module_list.html +20 -0
  199. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  200. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  201. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  202. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  203. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  204. nautobot/dcim/templates/dcim/module_update.html +71 -0
  205. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  206. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  207. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  208. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  209. nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
  210. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  211. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  212. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  213. nautobot/dcim/tests/test_api.py +693 -208
  214. nautobot/dcim/tests/test_filters.py +843 -217
  215. nautobot/dcim/tests/test_models.py +1103 -8
  216. nautobot/dcim/tests/test_views.py +1525 -343
  217. nautobot/dcim/urls.py +17 -2
  218. nautobot/dcim/utils.py +2 -3
  219. nautobot/dcim/views.py +1109 -113
  220. nautobot/extras/__init__.py +0 -1
  221. nautobot/extras/api/serializers.py +115 -3
  222. nautobot/extras/api/urls.py +12 -0
  223. nautobot/extras/api/views.py +73 -59
  224. nautobot/extras/apps.py +2 -2
  225. nautobot/extras/choices.py +43 -0
  226. nautobot/extras/context_managers.py +13 -8
  227. nautobot/extras/datasources/git.py +2 -0
  228. nautobot/extras/factory.py +460 -9
  229. nautobot/extras/filters/__init__.py +174 -3
  230. nautobot/extras/filters/mixins.py +46 -43
  231. nautobot/extras/forms/base.py +24 -5
  232. nautobot/extras/forms/forms.py +227 -8
  233. nautobot/extras/forms/mixins.py +93 -0
  234. nautobot/extras/graphql/types.py +23 -10
  235. nautobot/extras/homepage.py +26 -3
  236. nautobot/extras/jobs.py +2 -2
  237. nautobot/extras/management/__init__.py +1 -0
  238. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  239. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  240. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  241. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  242. nautobot/extras/migrations/0111_metadata.py +162 -0
  243. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  244. nautobot/extras/migrations/0113_saved_views.py +77 -0
  245. nautobot/extras/models/__init__.py +15 -1
  246. nautobot/extras/models/change_logging.py +3 -3
  247. nautobot/extras/models/contacts.py +4 -0
  248. nautobot/extras/models/customfields.py +18 -3
  249. nautobot/extras/models/groups.py +389 -225
  250. nautobot/extras/models/jobs.py +87 -3
  251. nautobot/extras/models/metadata.py +441 -0
  252. nautobot/extras/models/mixins.py +72 -62
  253. nautobot/extras/models/models.py +118 -9
  254. nautobot/extras/models/relationships.py +9 -2
  255. nautobot/extras/models/tags.py +13 -2
  256. nautobot/extras/navigation.py +57 -0
  257. nautobot/extras/plugins/__init__.py +3 -1
  258. nautobot/extras/querysets.py +30 -66
  259. nautobot/extras/signals.py +109 -101
  260. nautobot/extras/tables.py +201 -17
  261. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  262. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  263. nautobot/extras/templates/extras/job.html +1 -1
  264. nautobot/extras/templates/extras/job_detail.html +11 -0
  265. nautobot/extras/templates/extras/jobresult.html +61 -74
  266. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  267. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  268. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  269. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  270. nautobot/extras/templates/extras/plugins_list.html +1 -3
  271. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  272. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  273. nautobot/extras/tests/integration/test_customfields.py +1 -0
  274. nautobot/extras/tests/test_api.py +509 -23
  275. nautobot/extras/tests/test_changelog.py +20 -9
  276. nautobot/extras/tests/test_context_managers.py +22 -15
  277. nautobot/extras/tests/test_datasources.py +13 -1
  278. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  279. nautobot/extras/tests/test_filters.py +211 -12
  280. nautobot/extras/tests/test_jobs.py +6 -6
  281. nautobot/extras/tests/test_models.py +501 -4
  282. nautobot/extras/tests/test_relationships.py +1 -0
  283. nautobot/extras/tests/test_views.py +586 -8
  284. nautobot/extras/tests/test_webhooks.py +1 -1
  285. nautobot/extras/urls.py +5 -0
  286. nautobot/extras/utils.py +85 -16
  287. nautobot/extras/views.py +562 -122
  288. nautobot/ipam/__init__.py +0 -1
  289. nautobot/ipam/apps.py +1 -0
  290. nautobot/ipam/factory.py +17 -19
  291. nautobot/ipam/filters.py +13 -0
  292. nautobot/ipam/forms.py +8 -4
  293. nautobot/ipam/graphql/types.py +2 -2
  294. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  295. nautobot/ipam/models.py +20 -20
  296. nautobot/ipam/querysets.py +1 -1
  297. nautobot/ipam/signals.py +4 -2
  298. nautobot/ipam/tables.py +5 -0
  299. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  300. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  301. nautobot/ipam/templates/ipam/prefix.html +1 -0
  302. nautobot/ipam/tests/test_api.py +37 -18
  303. nautobot/ipam/tests/test_filters.py +26 -2
  304. nautobot/ipam/tests/test_models.py +9 -2
  305. nautobot/ipam/tests/test_querysets.py +1 -1
  306. nautobot/ipam/tests/test_views.py +3 -2
  307. nautobot/ipam/urls.py +2 -2
  308. nautobot/ipam/views.py +20 -34
  309. nautobot/project-static/css/base.css +21 -0
  310. nautobot/project-static/css/dark.css +11 -0
  311. nautobot/project-static/docs/404.html +894 -90
  312. nautobot/project-static/docs/apps/index.html +894 -90
  313. nautobot/project-static/docs/apps/nautobot-apps.html +894 -90
  314. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  315. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  316. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  317. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +921 -122
  318. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +906 -103
  319. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1620 -905
  320. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +937 -146
  321. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +979 -190
  322. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +903 -101
  323. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +899 -95
  324. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +993 -195
  325. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +976 -133
  326. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1080 -274
  327. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1244 -336
  328. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1729 -877
  329. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1166 -383
  330. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2090 -1376
  331. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2248 -1424
  332. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +914 -113
  333. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +965 -165
  334. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1012 -225
  335. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1915 -1279
  336. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1848 -1104
  337. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +906 -103
  338. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2335 -1701
  339. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1804 -1026
  340. nautobot/project-static/docs/development/apps/api/configuration-view.html +894 -90
  341. nautobot/project-static/docs/development/apps/api/database-backend-config.html +894 -90
  342. nautobot/project-static/docs/development/apps/api/models/django-admin.html +894 -90
  343. nautobot/project-static/docs/development/apps/api/models/global-search.html +894 -90
  344. nautobot/project-static/docs/development/apps/api/models/graphql.html +894 -90
  345. nautobot/project-static/docs/development/apps/api/models/index.html +944 -92
  346. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +894 -90
  347. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +894 -90
  348. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +894 -90
  349. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +894 -90
  350. nautobot/project-static/docs/development/apps/api/platform-features/index.html +894 -90
  351. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +894 -90
  352. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +894 -90
  353. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +894 -90
  354. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +894 -90
  355. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +894 -90
  356. nautobot/project-static/docs/development/apps/api/prometheus.html +894 -90
  357. nautobot/project-static/docs/development/apps/api/setup.html +894 -90
  358. nautobot/project-static/docs/development/apps/api/testing.html +894 -90
  359. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +894 -90
  360. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +894 -90
  361. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +894 -90
  362. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +894 -90
  363. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +894 -90
  364. nautobot/project-static/docs/development/apps/api/views/base-template.html +894 -90
  365. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +894 -90
  366. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +894 -90
  367. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +894 -90
  368. nautobot/project-static/docs/development/apps/api/views/index.html +894 -90
  369. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +894 -90
  370. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +894 -90
  371. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +894 -90
  372. nautobot/project-static/docs/development/apps/api/views/notes.html +894 -90
  373. nautobot/project-static/docs/development/apps/api/views/rest-api.html +894 -90
  374. nautobot/project-static/docs/development/apps/api/views/urls.html +894 -90
  375. nautobot/project-static/docs/development/apps/index.html +894 -90
  376. nautobot/project-static/docs/development/apps/migration/code-updates.html +894 -90
  377. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +894 -90
  378. nautobot/project-static/docs/development/apps/migration/from-v1.html +894 -90
  379. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +894 -90
  380. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +894 -90
  381. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +894 -90
  382. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +894 -90
  383. nautobot/project-static/docs/development/apps/porting-from-netbox.html +894 -90
  384. nautobot/project-static/docs/development/core/application-registry.html +894 -90
  385. nautobot/project-static/docs/development/core/best-practices.html +895 -90
  386. nautobot/project-static/docs/development/core/bootstrap-ui.html +894 -90
  387. nautobot/project-static/docs/development/core/caching.html +894 -90
  388. nautobot/project-static/docs/development/core/controllers.html +894 -90
  389. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +894 -90
  390. nautobot/project-static/docs/development/core/generic-views.html +894 -90
  391. nautobot/project-static/docs/development/core/getting-started.html +894 -90
  392. nautobot/project-static/docs/development/core/homepage.html +894 -90
  393. nautobot/project-static/docs/development/core/index.html +905 -90
  394. nautobot/project-static/docs/development/core/model-checklist.html +903 -91
  395. nautobot/project-static/docs/development/core/model-features.html +894 -90
  396. nautobot/project-static/docs/development/core/natural-keys.html +894 -90
  397. nautobot/project-static/docs/development/core/navigation-menu.html +894 -90
  398. nautobot/project-static/docs/development/core/release-checklist.html +897 -93
  399. nautobot/project-static/docs/development/core/role-internals.html +894 -90
  400. nautobot/project-static/docs/development/core/settings.html +894 -90
  401. nautobot/project-static/docs/development/core/style-guide.html +895 -91
  402. nautobot/project-static/docs/development/core/templates.html +906 -91
  403. nautobot/project-static/docs/development/core/testing.html +894 -90
  404. nautobot/project-static/docs/development/core/user-preferences.html +894 -90
  405. nautobot/project-static/docs/development/index.html +894 -90
  406. nautobot/project-static/docs/development/jobs/index.html +1271 -453
  407. nautobot/project-static/docs/development/jobs/migration/from-v1.html +894 -90
  408. nautobot/project-static/docs/index.html +9032 -13
  409. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  410. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  411. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  412. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  413. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  414. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  415. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  416. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  417. nautobot/project-static/docs/models/dcim/module.html +15 -0
  418. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  419. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  420. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  421. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  422. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  423. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  424. nautobot/project-static/docs/models/extras/role.html +15 -0
  425. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  426. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  427. nautobot/project-static/docs/models/extras/status.html +15 -0
  428. nautobot/project-static/docs/objects.inv +0 -0
  429. nautobot/project-static/docs/overview/application_stack.html +902 -91
  430. nautobot/project-static/docs/overview/design_philosophy.html +896 -92
  431. nautobot/project-static/docs/overview/index.html +13 -8228
  432. nautobot/project-static/docs/release-notes/index.html +1131 -94
  433. nautobot/project-static/docs/release-notes/version-1.0.html +894 -90
  434. nautobot/project-static/docs/release-notes/version-1.1.html +894 -90
  435. nautobot/project-static/docs/release-notes/version-1.2.html +894 -90
  436. nautobot/project-static/docs/release-notes/version-1.3.html +894 -90
  437. nautobot/project-static/docs/release-notes/version-1.4.html +894 -90
  438. nautobot/project-static/docs/release-notes/version-1.5.html +895 -91
  439. nautobot/project-static/docs/release-notes/version-1.6.html +895 -91
  440. nautobot/project-static/docs/release-notes/version-2.0.html +894 -90
  441. nautobot/project-static/docs/release-notes/version-2.1.html +894 -90
  442. nautobot/project-static/docs/release-notes/version-2.2.html +1137 -196
  443. nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
  444. nautobot/project-static/docs/requirements.txt +5 -5
  445. nautobot/project-static/docs/search/search_index.json +1 -1
  446. nautobot/project-static/docs/sitemap.xml +335 -260
  447. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +894 -90
  449. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +894 -90
  450. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +894 -90
  451. nautobot/project-static/docs/user-guide/administration/configuration/index.html +894 -90
  452. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +1025 -175
  453. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +894 -90
  454. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +894 -90
  455. nautobot/project-static/docs/user-guide/administration/guides/caching.html +894 -90
  456. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +902 -90
  457. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +894 -90
  458. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +894 -90
  459. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +894 -90
  460. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +894 -90
  461. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +894 -90
  462. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +894 -90
  463. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +894 -90
  464. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +894 -90
  465. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +946 -155
  466. nautobot/project-static/docs/user-guide/administration/installation/index.html +903 -95
  467. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +936 -124
  468. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +956 -159
  469. nautobot/project-static/docs/user-guide/administration/installation/services.html +915 -114
  470. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +910 -101
  471. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +894 -90
  472. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +894 -90
  473. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +894 -90
  474. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +894 -90
  475. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +977 -121
  476. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +894 -90
  477. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +894 -90
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +894 -90
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +894 -90
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +894 -90
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +894 -90
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +894 -90
  483. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +894 -90
  484. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +894 -90
  485. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +894 -90
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +894 -90
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +895 -91
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +894 -90
  489. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +898 -90
  490. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +897 -93
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  496. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  497. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +908 -104
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +925 -107
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +925 -107
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +920 -102
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +925 -107
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +908 -104
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +908 -104
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +915 -107
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +922 -118
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +923 -119
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +920 -116
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +908 -104
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +916 -107
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +928 -110
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +938 -120
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +930 -108
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +908 -104
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +939 -121
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +930 -112
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +920 -116
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +923 -119
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +925 -117
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +918 -114
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +908 -104
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +942 -85
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +926 -108
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +908 -104
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +945 -88
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +923 -105
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +931 -127
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +920 -116
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +908 -104
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +924 -106
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +926 -108
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +908 -104
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +908 -104
  538. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +908 -104
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +938 -90
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +894 -90
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +899 -91
  542. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +899 -91
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +894 -90
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +894 -90
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +894 -90
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +894 -90
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +894 -90
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +894 -90
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +894 -90
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +894 -90
  551. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +894 -90
  552. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +894 -90
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +903 -98
  554. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +894 -90
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +894 -90
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +894 -90
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +894 -90
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +894 -90
  559. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +899 -91
  560. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +894 -90
  561. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +894 -90
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +894 -90
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +894 -90
  564. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +894 -90
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +894 -90
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +894 -90
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +894 -90
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +894 -90
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +894 -90
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +894 -90
  571. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +894 -90
  572. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +894 -90
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  592. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  593. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  594. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  595. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  596. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  597. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  598. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +894 -90
  599. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +894 -90
  600. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +894 -90
  601. nautobot/project-static/docs/user-guide/index.html +894 -90
  602. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +894 -90
  603. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +894 -90
  604. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +894 -90
  605. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +894 -90
  606. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1260 -787
  607. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +897 -93
  608. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +894 -90
  609. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +894 -90
  610. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +894 -90
  611. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +894 -90
  612. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +894 -90
  613. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +894 -90
  614. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +894 -90
  615. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +894 -90
  616. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +894 -90
  617. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +898 -90
  618. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +894 -90
  619. nautobot/project-static/docs/user-guide/platform-functionality/note.html +897 -93
  620. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
  621. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +897 -93
  622. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +894 -90
  623. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +894 -90
  624. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +894 -90
  625. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +894 -90
  626. nautobot/project-static/docs/user-guide/platform-functionality/role.html +897 -93
  627. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  628. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +897 -93
  629. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  630. nautobot/project-static/docs/user-guide/platform-functionality/status.html +894 -90
  631. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +894 -90
  632. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +952 -123
  633. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +894 -90
  634. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +894 -90
  635. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +894 -90
  636. nautobot/project-static/js/forms.js +71 -0
  637. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  638. nautobot/project-static/js/tableconfig.js +6 -1
  639. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  640. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  641. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  642. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  643. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  644. nautobot/tenancy/__init__.py +0 -1
  645. nautobot/tenancy/apps.py +1 -0
  646. nautobot/tenancy/factory.py +3 -2
  647. nautobot/tenancy/filters/__init__.py +1 -0
  648. nautobot/tenancy/forms.py +1 -1
  649. nautobot/tenancy/templates/tenancy/tenant.html +24 -20
  650. nautobot/tenancy/views.py +11 -10
  651. nautobot/users/__init__.py +0 -1
  652. nautobot/users/api/serializers.py +1 -1
  653. nautobot/users/api/views.py +4 -2
  654. nautobot/users/apps.py +3 -2
  655. nautobot/users/factory.py +3 -3
  656. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  657. nautobot/users/models.py +12 -0
  658. nautobot/users/tests/test_filters.py +6 -3
  659. nautobot/users/urls.py +8 -0
  660. nautobot/virtualization/__init__.py +0 -1
  661. nautobot/virtualization/apps.py +1 -0
  662. nautobot/virtualization/filters.py +6 -1
  663. nautobot/virtualization/forms.py +11 -3
  664. nautobot/virtualization/graphql/types.py +2 -2
  665. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  666. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  667. nautobot/virtualization/models.py +0 -2
  668. nautobot/virtualization/tables.py +12 -8
  669. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  670. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  671. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  672. nautobot/virtualization/tests/test_api.py +9 -4
  673. nautobot/virtualization/tests/test_filters.py +22 -0
  674. nautobot/virtualization/tests/test_models.py +7 -3
  675. nautobot/virtualization/tests/test_views.py +19 -3
  676. nautobot/virtualization/urls.py +2 -2
  677. nautobot/virtualization/views.py +10 -32
  678. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/METADATA +21 -19
  679. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/RECORD +684 -564
  680. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  681. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  682. nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
  683. nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
  684. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
  685. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
  686. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
  687. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
  688. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
  689. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
  690. nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
  691. nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
  692. nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
  693. nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
  694. nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
  695. nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
  696. nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
  697. nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
  698. nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
  699. nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
  700. /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
  701. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
  702. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
  703. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
  704. {nautobot-2.2.8.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -45,7 +45,7 @@ def render_form(form, excluded_fields=None):
45
45
  https://github.com/nautobot/nautobot/issues/4503
46
46
  """
47
47
  if excluded_fields is None:
48
- excluded_fields = ["tags", "object_note"]
48
+ excluded_fields = ["tags", "dynamic_groups", "object_note"]
49
49
 
50
50
  return {
51
51
  "form": form,
@@ -2,10 +2,14 @@ import datetime
2
2
  import json
3
3
  import logging
4
4
  import re
5
+ from urllib.parse import parse_qs
5
6
 
6
7
  from django import template
7
8
  from django.conf import settings
9
+ from django.contrib import messages
10
+ from django.contrib.auth.models import AnonymousUser
8
11
  from django.contrib.staticfiles.finders import find
12
+ from django.core.exceptions import ObjectDoesNotExist
9
13
  from django.templatetags.static import static, StaticNode
10
14
  from django.urls import NoReverseMatch, reverse
11
15
  from django.utils.html import format_html, format_html_join
@@ -192,7 +196,7 @@ def render_markdown(value):
192
196
 
193
197
  @library.filter()
194
198
  @register.filter()
195
- def render_json(value, syntax_highlight=True):
199
+ def render_json(value, syntax_highlight=True, pretty_print=False):
196
200
  """
197
201
  Render a dictionary as formatted JSON.
198
202
 
@@ -201,7 +205,11 @@ def render_json(value, syntax_highlight=True):
201
205
  """
202
206
  rendered_json = json.dumps(value, indent=4, sort_keys=True, ensure_ascii=False)
203
207
  if syntax_highlight:
204
- return format_html('<code class="language-json">{}</code>', rendered_json)
208
+ html_string = '<code class="language-json">{}</code>'
209
+ if pretty_print:
210
+ html_string = "<pre>" + html_string + "</pre>"
211
+ return format_html(html_string, rendered_json)
212
+
205
213
  return rendered_json
206
214
 
207
215
 
@@ -581,6 +589,33 @@ def slugify(value):
581
589
  return django_slugify(value)
582
590
 
583
591
 
592
+ @library.filter()
593
+ @register.filter()
594
+ def render_uptime(seconds):
595
+ """Format a value in seconds to a human readable value.
596
+
597
+ Example:
598
+ >>> render_uptime(1024768)
599
+ "11 days 20 hours 39 minutes"
600
+ """
601
+ try:
602
+ seconds = int(seconds)
603
+ except ValueError:
604
+ return placeholder(seconds)
605
+ delta = datetime.timedelta(seconds=seconds)
606
+ uptime_hours = delta.seconds // 3600
607
+ uptime_minutes = delta.seconds // 60 % 60
608
+ return format_html(
609
+ "{} {} {} {} {} {}",
610
+ delta.days,
611
+ "days" if delta.days != 1 else "day",
612
+ uptime_hours,
613
+ "hours" if uptime_hours != 1 else "hour",
614
+ uptime_minutes,
615
+ "minutes" if uptime_minutes != 1 else "minute",
616
+ )
617
+
618
+
584
619
  #
585
620
  # Tags
586
621
  #
@@ -609,6 +644,23 @@ def querystring(request, **kwargs):
609
644
  return ""
610
645
 
611
646
 
647
+ @register.simple_tag()
648
+ def table_config_button(table, table_name=None, extra_classes=""):
649
+ if table_name is None:
650
+ table_name = table.__class__.__name__
651
+ html_template = (
652
+ '<button type="button" class="btn btn-default {}'
653
+ '" data-toggle="modal" data-target="#{}_config" title="Configure table">'
654
+ '<i class="mdi mdi-cog"></i> Configure</button>'
655
+ )
656
+ return format_html(html_template, extra_classes, table_name)
657
+
658
+
659
+ @register.simple_tag()
660
+ def table_config_button_small(table, table_name=None):
661
+ return table_config_button(table, table_name, "btn-xs")
662
+
663
+
612
664
  @register.inclusion_tag("utilities/templatetags/utilization_graph.html")
613
665
  def utilization_graph(utilization_data, warning_threshold=75, danger_threshold=90):
614
666
  """Wrapper for a horizontal bar graph indicating a percentage of utilization from a tuple of data.
@@ -714,6 +766,121 @@ def filter_form_modal(
714
766
  }
715
767
 
716
768
 
769
+ @register.inclusion_tag("utilities/templatetags/saved_view_modal.html")
770
+ def saved_view_modal(
771
+ params,
772
+ view,
773
+ model,
774
+ request,
775
+ ):
776
+ from nautobot.extras.forms import SavedViewModalForm
777
+ from nautobot.extras.models import SavedView
778
+
779
+ param_dict = {}
780
+ filters_applied = parse_qs(params)
781
+
782
+ sort_order = []
783
+ per_page = None
784
+ table_changes_pending = False
785
+ all_filters_removed = False
786
+ current_saved_view = None
787
+ current_saved_view_pk = None
788
+ non_filter_params = [
789
+ "all_filters_removed",
790
+ "page",
791
+ "per_page",
792
+ "sort",
793
+ "saved_view",
794
+ "table_changes_pending",
795
+ "clear_view",
796
+ ]
797
+
798
+ table_name = lookup.get_table_for_model(model).__name__
799
+ for param in non_filter_params:
800
+ if param == "saved_view":
801
+ current_saved_view_pk = filters_applied.pop(param, None)
802
+ if current_saved_view_pk:
803
+ current_saved_view_pk = current_saved_view_pk[0]
804
+ try:
805
+ # We are not using .restrict(request.user, "view") here
806
+ # User should be able to see any saved view that he has the list view access to.
807
+ current_saved_view = SavedView.objects.get(pk=current_saved_view_pk)
808
+ except ObjectDoesNotExist:
809
+ messages.error(request, f"Saved view {current_saved_view_pk} not found")
810
+
811
+ elif param == "table_changes_pending":
812
+ table_changes_pending = filters_applied.pop(param, False)
813
+ elif param == "all_filters_removed":
814
+ all_filters_removed = filters_applied.pop(param, False)
815
+ elif param == "per_page":
816
+ per_page = filters_applied.pop(param, None)
817
+ elif param == "sort":
818
+ sort_order = filters_applied.pop(param, [])
819
+ elif param == "clear_view":
820
+ filters_applied.pop(param, False)
821
+
822
+ if filters_applied:
823
+ param_dict["filter_params"] = filters_applied
824
+ else:
825
+ if (current_saved_view is not None and all_filters_removed) or (current_saved_view is None):
826
+ # user removed all the filters in a saved view
827
+ param_dict["filter_params"] = {}
828
+ elif current_saved_view is not None:
829
+ # user did not make any changes to the saved view filter params
830
+ param_dict["filter_params"] = current_saved_view.config.get("filter_params", {})
831
+
832
+ if current_saved_view is not None and not table_changes_pending:
833
+ # user did not make any changes to the saved view table config
834
+ view_table_config = current_saved_view.config.get("table_config", {}).get(f"{table_name}", None)
835
+ if view_table_config is not None:
836
+ param_dict["table_config"] = view_table_config.get("columns", [])
837
+ else:
838
+ # display default user display
839
+ if request.user is not None and not isinstance(request.user, AnonymousUser):
840
+ param_dict["table_config"] = request.user.get_config(f"tables.{table_name}.columns")
841
+ # If both are not available, do not display table_config
842
+
843
+ if per_page:
844
+ # user made changes to saved view pagination count
845
+ param_dict["per_page"] = per_page
846
+ elif current_saved_view is not None and not per_page:
847
+ # no changes made, display current saved view pagination count
848
+ param_dict["per_page"] = current_saved_view.config.get(
849
+ "pagination_count", config.get_settings_or_config("PAGINATE_COUNT")
850
+ )
851
+ else:
852
+ # display default pagination count
853
+ param_dict["per_page"] = config.get_settings_or_config("PAGINATE_COUNT")
854
+
855
+ if sort_order:
856
+ # user made changes to saved view sort order
857
+ param_dict["sort_order"] = sort_order
858
+ elif current_saved_view is not None and not sort_order:
859
+ # no changes made, display current saved view sort order
860
+ param_dict["sort_order"] = current_saved_view.config.get("sort_order", [])
861
+ else:
862
+ # no sorting applied
863
+ param_dict["sort_order"] = []
864
+
865
+ param_dict = json.dumps(param_dict, indent=4, sort_keys=True, ensure_ascii=False)
866
+ return {
867
+ "form": SavedViewModalForm(),
868
+ "params": params,
869
+ "param_dict": param_dict,
870
+ "view": view,
871
+ }
872
+
873
+
874
+ @register.inclusion_tag("utilities/templatetags/dynamic_group_assignment_modal.html")
875
+ def dynamic_group_assignment_modal(request, content_type):
876
+ from nautobot.extras.forms import DynamicGroupBulkAssignForm
877
+
878
+ return {
879
+ "request": request,
880
+ "form": DynamicGroupBulkAssignForm(model=content_type.model_class()),
881
+ }
882
+
883
+
717
884
  @register.inclusion_tag("utilities/templatetags/modal_form_as_dialog.html")
718
885
  def modal_form_as_dialog(form, editing=False, form_name=None, obj=None, obj_type=None):
719
886
  """Generate a form in a modal view.
@@ -806,7 +973,7 @@ def hyperlinked_object_with_color(obj):
806
973
  if obj:
807
974
  content = f'<span class="label" style="color: {fgcolor(obj.color)}; background-color: #{obj.color}">{hyperlinked_object(obj)}</span>'
808
975
  return format_html(content)
809
- return "—"
976
+ return HTML_NONE
810
977
 
811
978
 
812
979
  @register.filter()
@@ -870,12 +1037,15 @@ def _build_hyperlink(value, field="", target="", rel=""):
870
1037
  attributes = {}
871
1038
  display = getattr(value, field) if hasattr(value, field) else str(value)
872
1039
  if hasattr(value, "get_absolute_url"):
873
- attributes["href"] = value.get_absolute_url()
874
- if hasattr(value, "description") and value.description:
875
- attributes["title"] = value.description
876
- if target:
877
- attributes["target"] = target
878
- if rel:
879
- attributes["rel"] = rel
880
- return format_html("<a {}>{}</a>", format_html_join(" ", '{}="{}"', attributes.items()), display)
1040
+ try:
1041
+ attributes["href"] = value.get_absolute_url()
1042
+ if hasattr(value, "description") and value.description:
1043
+ attributes["title"] = value.description
1044
+ if target:
1045
+ attributes["target"] = target
1046
+ if rel:
1047
+ attributes["rel"] = rel
1048
+ return format_html("<a {}>{}</a>", format_html_join(" ", '{}="{}"', attributes.items()), display)
1049
+ except AttributeError:
1050
+ pass
881
1051
  return format_html("{}", display)
@@ -740,7 +740,11 @@ class APIViewTestCases:
740
740
 
741
741
  initial_count = self._get_queryset().count()
742
742
  response = self.client.post(self._get_list_url(), self.create_data, format="json", **self.header)
743
- self.assertHttpStatus(response, status.HTTP_201_CREATED)
743
+ self.assertHttpStatus(
744
+ response,
745
+ status.HTTP_201_CREATED,
746
+ msg=f"create_data: {self.create_data}\nexisting records: {list(self._get_queryset())}",
747
+ )
744
748
  self.assertEqual(len(response.data), len(self.create_data))
745
749
  self.assertEqual(self._get_queryset().count(), initial_count + len(self.create_data))
746
750
  for i, obj in enumerate(response.data):
@@ -841,7 +845,6 @@ class APIViewTestCases:
841
845
  # This may change (hah) at some point -- see https://github.com/nautobot/nautobot/issues/3321
842
846
  if hasattr(self.model, "to_objectchange"):
843
847
  objectchanges = lookup.get_changes_for_model(instance)
844
- self.assertEqual(len(objectchanges), 1)
845
848
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
846
849
  objectchanges.delete()
847
850
 
@@ -858,7 +861,6 @@ class APIViewTestCases:
858
861
  # Verify ObjectChange creation
859
862
  if hasattr(self.model, "to_objectchange"):
860
863
  objectchanges = lookup.get_changes_for_model(instance)
861
- self.assertEqual(len(objectchanges), 1)
862
864
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
863
865
 
864
866
  def test_get_put_round_trip(self):
@@ -1030,7 +1032,6 @@ class APIViewTestCases:
1030
1032
  # Verify ObjectChange creation
1031
1033
  if hasattr(self.model, "to_objectchange"):
1032
1034
  objectchanges = lookup.get_changes_for_model(instance)
1033
- self.assertEqual(len(objectchanges), 1)
1034
1035
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_DELETE)
1035
1036
 
1036
1037
  def test_bulk_delete_objects(self):
@@ -3,10 +3,12 @@ import string
3
3
 
4
4
  from django.contrib.contenttypes.models import ContentType
5
5
  from django.db.models import Count, Q
6
+ from django.db.models.fields import CharField, TextField
6
7
  from django.db.models.fields.related import ManyToManyField
7
8
  from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
8
9
  from django.test import tag
9
10
 
11
+ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
10
12
  from nautobot.core.filters import (
11
13
  ContentTypeChoiceFilter,
12
14
  ContentTypeFilter,
@@ -16,6 +18,7 @@ from nautobot.core.filters import (
16
18
  )
17
19
  from nautobot.core.models.generics import PrimaryModel
18
20
  from nautobot.core.testing import views
21
+ from nautobot.extras.models import Contact, ContactAssociation, Role, Status, Tag, Team
19
22
  from nautobot.tenancy import models
20
23
 
21
24
 
@@ -119,6 +122,42 @@ class FilterTestCases:
119
122
  )
120
123
  This expects a field named `devices` on the model and a filter named `devices` on the filterset.
121
124
  """
125
+ if getattr(self.queryset.model, "is_contact_associable_model", False):
126
+ for generic_filter_test in (
127
+ ["contacts", "associated_contacts__contact__name"],
128
+ ["contacts", "associated_contacts__contact__id"],
129
+ ["teams", "associated_contacts__team__name"],
130
+ ["teams", "associated_contacts__team__id"],
131
+ ):
132
+ if generic_filter_test not in self.generic_filter_tests:
133
+ self.generic_filter_tests = (*self.generic_filter_tests, generic_filter_test)
134
+
135
+ # Make sure we have at least 3 contacts and 3 teams in the database
136
+ if Contact.objects.count() < 3:
137
+ Contact.objects.create(name="Generic Filter Test Contact 1")
138
+ Contact.objects.create(name="Generic Filter Test Contact 2")
139
+ Contact.objects.create(name="Generic Filter Test Contact 3")
140
+
141
+ if Team.objects.count() < 3:
142
+ Team.objects.create(name="Generic Filter Test Team 1")
143
+ Team.objects.create(name="Generic Filter Test Team 2")
144
+ Team.objects.create(name="Generic Filter Test Team 3")
145
+
146
+ # Make sure we have some valid contact-associations:
147
+ for contact, team, instance in zip(Contact.objects.all()[:3], Team.objects.all()[:3], self.queryset):
148
+ ContactAssociation.objects.create(
149
+ contact=contact,
150
+ associated_object=instance,
151
+ role=Role.objects.get_for_model(ContactAssociation).first(),
152
+ status=Status.objects.get_for_model(ContactAssociation).first(),
153
+ )
154
+ ContactAssociation.objects.create(
155
+ team=team,
156
+ associated_object=instance,
157
+ role=Role.objects.get_for_model(ContactAssociation).last(),
158
+ status=Status.objects.get_for_model(ContactAssociation).last(),
159
+ )
160
+
122
161
  if not self.generic_filter_tests:
123
162
  self.skipTest("No generic_filter_tests defined?")
124
163
 
@@ -147,11 +186,11 @@ class FilterTestCases:
147
186
  field_name = filter_object.field_name
148
187
  with self.subTest(f"{self.filterset.__name__} RelatedMembershipBooleanFilter {filter_name} (True)"):
149
188
  filterset_result = self.filterset({filter_name: True}, self.queryset).qs
150
- qs_result = self.queryset.filter(**{f"{field_name}__isnull": False}).distinct()
189
+ qs_result = self.queryset.filter(**{f"{field_name}__isnull": filter_object.exclude}).distinct()
151
190
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
152
191
  with self.subTest(f"{self.filterset.__name__} RelatedMembershipBooleanFilter {filter_name} (False)"):
153
192
  filterset_result = self.filterset({filter_name: False}, self.queryset).qs
154
- qs_result = self.queryset.filter(**{f"{field_name}__isnull": True}).distinct()
193
+ qs_result = self.queryset.exclude(**{f"{field_name}__isnull": filter_object.exclude}).distinct()
155
194
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
156
195
 
157
196
  def test_tags_filter(self):
@@ -164,8 +203,16 @@ class FilterTestCases:
164
203
  if len(instance.tags.all()) >= 2:
165
204
  tags = list(instance.tags.all()[:2])
166
205
  break
206
+
207
+ # Otherwise, create some tags and apply to an instance for this test
167
208
  else:
168
- self.fail(f"Couldn't find any {self.queryset.model._meta.object_name} with at least two Tags.")
209
+ model_ct = ContentType.objects.get_for_model(self.queryset.model)
210
+ test_tags_filter_a = Tag.objects.get_or_create(name="test tags filter a")[0]
211
+ test_tags_filter_a.content_types.add(model_ct)
212
+ test_tags_filter_b = Tag.objects.get_or_create(name="test tags filter b")[0]
213
+ test_tags_filter_b.content_types.add(model_ct)
214
+ self.queryset.first().tags.add(test_tags_filter_a, test_tags_filter_b)
215
+ tags = [test_tags_filter_a, test_tags_filter_b]
169
216
  params = {"tags": [tags[0].name, tags[1].pk]}
170
217
  filterset_result = self.filterset(params, self.queryset).qs
171
218
  # Tags is an AND filter not an OR filter
@@ -222,22 +269,23 @@ class FilterTestCases:
222
269
  """
223
270
  self._assert_valid_filter_predicates(obj, obj_field_name)
224
271
 
225
- # Create random 5 char string to append to attribute, used for icontains partial lookup
226
- lookup = "".join(random.choices(string.ascii_lowercase, k=5)) # noqa: S311 # pseudo-random generator
227
- obj_field_value = getattr(obj, obj_field_name)
272
+ # Generic test only supports CharField or TextFields, skip all other types
273
+ obj_field = obj._meta.get_field(obj_field_name)
274
+ if not isinstance(obj_field, (CharField, TextField)):
275
+ self.skipTest("Not a CharField or TextField")
228
276
 
229
- if isinstance(obj_field_value, str):
230
- updated_attr = obj_field_value + lookup
231
- setattr(obj, obj_field_name, updated_attr)
232
- else:
233
- # Skip the test if the field is not a CharField, as we currently only support generic testing for CharField
234
- self.skipTest("Not a CharField")
277
+ # Create random lowercase string to use for icontains lookup
278
+ max_length = obj_field.max_length or CHARFIELD_MAX_LENGTH
279
+ randomized_attr_value = "".join(random.choices(string.ascii_lowercase, k=max_length)) # noqa: S311 # pseudo-random generator
280
+ setattr(obj, obj_field_name, randomized_attr_value)
235
281
  obj.save()
282
+
236
283
  # if lookup_method is iexact use the full updated attr
237
284
  if lookup_method == "iexact":
238
- lookup = updated_attr
239
- model_queryset = self.queryset.filter(**{f"{filter_field_name}": lookup})
285
+ lookup = randomized_attr_value.upper()
286
+ model_queryset = self.queryset.filter(**{f"{filter_field_name}__iexact": lookup})
240
287
  else:
288
+ lookup = randomized_attr_value[1:].upper()
241
289
  model_queryset = self.queryset.filter(**{f"{filter_field_name}__icontains": lookup})
242
290
  params = {"q": lookup}
243
291
  filterset_result = self.filterset(params, self.queryset)
@@ -247,6 +295,7 @@ class FilterTestCases:
247
295
  filterset_result.qs,
248
296
  model_queryset,
249
297
  ordered=False,
298
+ msg=lookup,
250
299
  )
251
300
 
252
301
  def _get_relevant_filterset_queryset(self, queryset, *filter_params):
@@ -6,8 +6,10 @@ from django.contrib.auth import get_user_model
6
6
  from django.contrib.contenttypes.models import ContentType
7
7
  from django.core.cache import cache
8
8
  from django.core.exceptions import FieldDoesNotExist
9
+ from django.db import connections, DEFAULT_DB_ALIAS
9
10
  from django.db.models import JSONField, ManyToManyField, ManyToManyRel
10
11
  from django.forms.models import model_to_dict
12
+ from django.test.utils import CaptureQueriesContext
11
13
  from netaddr import IPNetwork
12
14
  from rest_framework.test import APIClient, APIRequestFactory
13
15
 
@@ -44,6 +46,7 @@ class NautobotTestCaseMixin:
44
46
 
45
47
  user_permissions = ()
46
48
  client_class = NautobotTestClient
49
+ maxDiff = None
47
50
 
48
51
  def setUpNautobot(self, client=True, populate_status=False):
49
52
  """Setup shared testuser, statuses and client."""
@@ -231,6 +234,49 @@ class NautobotTestCaseMixin:
231
234
 
232
235
  return self.assertQuerysetEqual(qs, values, *args, **kwargs)
233
236
 
237
+ class _AssertApproximateNumQueriesContext(CaptureQueriesContext):
238
+ """Implementation class underlying the assertApproximateNumQueries decorator/context manager."""
239
+
240
+ def __init__(self, test_case, minimum, maximum, connection):
241
+ self.test_case = test_case
242
+ self.minimum = minimum
243
+ self.maximum = maximum
244
+ super().__init__(connection)
245
+
246
+ def __exit__(self, exc_type, exc_value, traceback):
247
+ super().__exit__(exc_type, exc_value, traceback)
248
+ if exc_type is not None:
249
+ return
250
+ num_queries = len(self)
251
+ captured_queries_string = "\n".join(
252
+ f"{i}. {query['sql']}" for i, query in enumerate(self.captured_queries, start=1)
253
+ )
254
+ self.test_case.assertGreaterEqual(
255
+ num_queries,
256
+ self.minimum,
257
+ f"{num_queries} queries executed, but expected at least {self.minimum}.\n"
258
+ f"Captured queries were:\n{captured_queries_string}",
259
+ )
260
+ self.test_case.assertLessEqual(
261
+ num_queries,
262
+ self.maximum,
263
+ f"{num_queries} queries executed, but expected no more than {self.maximum}.\n"
264
+ f"Captured queries were:\n{captured_queries_string}",
265
+ )
266
+
267
+ def assertApproximateNumQueries(self, minimum, maximum, func=None, *args, using=DEFAULT_DB_ALIAS, **kwargs):
268
+ """Like assertNumQueries, but fuzzier. Assert that the number of queries falls within an acceptable range."""
269
+ conn = connections[using]
270
+
271
+ context = self._AssertApproximateNumQueriesContext(self, minimum, maximum, conn)
272
+ if func is None:
273
+ return context
274
+
275
+ with context:
276
+ func(*args, **kwargs)
277
+
278
+ return None
279
+
234
280
  #
235
281
  # Convenience methods
236
282
  #
@@ -1,7 +1,9 @@
1
+ from django.db.models import QuerySet
1
2
  from django.test import tag, TestCase
2
3
 
3
4
  from nautobot.core.templatetags.helpers import get_docs_url
4
5
  from nautobot.core.testing.mixins import NautobotTestCaseMixin
6
+ from nautobot.extras.models import DynamicGroup, StaticGroupAssociation
5
7
 
6
8
 
7
9
  @tag("unit")
@@ -41,3 +43,23 @@ class ModelTestCases:
41
43
  def test_get_docs_url(self):
42
44
  """Check that `get_docs_url()` returns a valid static file path for this model."""
43
45
  self.assertIsNotNone(get_docs_url(self.model))
46
+
47
+ def test_dynamic_group_api(self):
48
+ """For dynamic-group capable models, check that they work as intended."""
49
+ if not getattr(self.model, "is_dynamic_group_associable_model", False):
50
+ self.skipTest("Not a dynamic group associable model.")
51
+
52
+ self.assertTrue(hasattr(self.model, "dynamic_groups"))
53
+ self.assertIsInstance(self.model.objects.first().dynamic_groups, QuerySet)
54
+ self.assertEqual(self.model.objects.first().dynamic_groups.model, DynamicGroup)
55
+
56
+ if DynamicGroup.objects.get_for_model(self.model).exists():
57
+ dg = DynamicGroup.objects.get_for_model(self.model).first()
58
+ self.assertEqual(dg.members.model, self.model)
59
+
60
+ # Models using DynamicGroupMixin w/o DynamicGroupsModelMixin will not have static_group_association_set
61
+ if hasattr(self.model, "static_group_association_set"):
62
+ self.assertIsInstance(self.model.objects.first().static_group_association_set.all(), QuerySet)
63
+ self.assertEqual(
64
+ self.model.objects.first().static_group_association_set.all().model, StaticGroupAssociation
65
+ )
@@ -1,10 +1,7 @@
1
- from io import StringIO
2
-
3
1
  from django.conf import settings
4
- from django.core.management import call_command
5
2
  from django.test import tag
3
+ from drf_spectacular.settings import spectacular_settings
6
4
  from rest_framework.settings import api_settings
7
- import yaml
8
5
 
9
6
  from nautobot.core.testing import views
10
7
 
@@ -19,11 +16,10 @@ class OpenAPISchemaTestCases:
19
16
  # We could load the schema from the /api/swagger.yaml endpoint in setUp(self) via self.client,
20
17
  # but it's fairly expensive to do so. Better to do so only once per class.
21
18
  cls.schemas = {}
19
+ generator_class = spectacular_settings.DEFAULT_GENERATOR_CLASS
22
20
  for api_version in api_settings.ALLOWED_VERSIONS:
23
- out = StringIO()
24
- err = StringIO()
25
- call_command("spectacular", "--api-version", api_version, stdout=out, stderr=err)
26
- cls.schemas[api_version] = yaml.safe_load(out.getvalue())
21
+ generator = generator_class(api_version=api_version)
22
+ cls.schemas[api_version] = generator.get_schema(request=None, public=True)
27
23
 
28
24
  def get_component_schema(self, component_name, api_version=None):
29
25
  """Helper method to pull a specific component schema from the larger OpenAPI schema already loaded."""