nautobot 2.2.9__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 (697) 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 +95 -13
  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/schema.py +26 -4
  61. nautobot/core/jobs/__init__.py +16 -2
  62. nautobot/core/jobs/cleanup.py +100 -0
  63. nautobot/core/jobs/groups.py +38 -0
  64. nautobot/core/management/commands/generate_test_data.py +116 -3
  65. nautobot/core/models/__init__.py +34 -9
  66. nautobot/core/models/generics.py +19 -3
  67. nautobot/core/models/name_color_content_types.py +7 -28
  68. nautobot/core/models/querysets.py +4 -3
  69. nautobot/core/models/tree_queries.py +1 -1
  70. nautobot/core/models/utils.py +21 -5
  71. nautobot/core/settings.py +2 -17
  72. nautobot/core/settings.yaml +34 -13
  73. nautobot/core/settings_funcs.py +103 -0
  74. nautobot/core/tables.py +130 -56
  75. nautobot/core/templates/admin/search_form.html +1 -1
  76. nautobot/core/templates/buttons/add.html +11 -3
  77. nautobot/core/templates/buttons/consolidated_bulk_action_buttons.html +13 -0
  78. nautobot/core/templates/buttons/consolidated_detail_view_action_buttons.html +13 -0
  79. nautobot/core/templates/buttons/export.html +101 -53
  80. nautobot/core/templates/buttons/job_import.html +11 -3
  81. nautobot/core/templates/generic/object_bulk_destroy.html +3 -1
  82. nautobot/core/templates/generic/object_bulk_update.html +3 -1
  83. nautobot/core/templates/generic/object_changelog.html +0 -9
  84. nautobot/core/templates/generic/object_list.html +156 -17
  85. nautobot/core/templates/generic/object_retrieve.html +80 -16
  86. nautobot/core/templates/inc/extras_features_edit_form_fields.html +8 -0
  87. nautobot/core/templates/inc/javascript.html +2 -0
  88. nautobot/core/templates/inc/media.html +2 -2
  89. nautobot/core/templates/inc/nav_menu.html +1 -0
  90. nautobot/core/templates/inc/paginator.html +7 -7
  91. nautobot/core/templates/inc/search_panel.html +2 -2
  92. nautobot/core/templates/inc/table.html +2 -2
  93. nautobot/core/templates/nautobot_config.py.j2 +13 -8
  94. nautobot/core/templates/utilities/templatetags/dynamic_group_assignment_modal.html +37 -0
  95. nautobot/core/templates/utilities/templatetags/filter_form_modal.html +2 -2
  96. nautobot/core/templates/utilities/templatetags/saved_view_modal.html +38 -0
  97. nautobot/core/templates/utilities/theme_preview.html +25 -8
  98. nautobot/core/templates/utilities/worker_status.html +152 -0
  99. nautobot/core/templatetags/buttons.py +335 -38
  100. nautobot/core/templatetags/form_helpers.py +1 -1
  101. nautobot/core/templatetags/helpers.py +181 -11
  102. nautobot/core/testing/api.py +5 -4
  103. nautobot/core/testing/filters.py +63 -14
  104. nautobot/core/testing/mixins.py +46 -0
  105. nautobot/core/testing/models.py +22 -0
  106. nautobot/core/testing/schema.py +4 -8
  107. nautobot/core/testing/views.py +31 -14
  108. nautobot/core/tests/integration/test_import_objects_ui.py +1 -0
  109. nautobot/core/tests/integration/test_swagger.py +1 -1
  110. nautobot/core/tests/nautobot_config.py +0 -1
  111. nautobot/core/tests/runner.py +2 -2
  112. nautobot/core/tests/test_api.py +1 -0
  113. nautobot/core/tests/test_authentication.py +7 -2
  114. nautobot/core/tests/test_filters.py +11 -9
  115. nautobot/core/tests/test_forms.py +9 -0
  116. nautobot/core/tests/test_graphql.py +27 -16
  117. nautobot/core/tests/test_jobs.py +122 -0
  118. nautobot/core/tests/test_tables.py +3 -1
  119. nautobot/core/tests/test_templatetags_helpers.py +12 -5
  120. nautobot/core/tests/test_utils.py +31 -20
  121. nautobot/core/tests/test_views.py +6 -6
  122. nautobot/core/urls.py +8 -3
  123. nautobot/core/utils/deprecation.py +29 -0
  124. nautobot/core/utils/filtering.py +12 -9
  125. nautobot/core/utils/lookup.py +37 -2
  126. nautobot/core/utils/requests.py +4 -1
  127. nautobot/core/views/__init__.py +137 -24
  128. nautobot/core/views/generic.py +119 -67
  129. nautobot/core/views/mixins.py +105 -36
  130. nautobot/core/views/paginator.py +9 -3
  131. nautobot/core/views/renderers.py +121 -56
  132. nautobot/core/views/utils.py +81 -1
  133. nautobot/dcim/__init__.py +0 -1
  134. nautobot/dcim/api/serializers.py +180 -44
  135. nautobot/dcim/api/urls.py +7 -3
  136. nautobot/dcim/api/views.py +53 -7
  137. nautobot/dcim/apps.py +3 -0
  138. nautobot/dcim/choices.py +25 -0
  139. nautobot/dcim/constants.py +7 -0
  140. nautobot/dcim/factory.py +252 -18
  141. nautobot/dcim/filters/__init__.py +373 -193
  142. nautobot/dcim/filters/mixins.py +274 -1
  143. nautobot/dcim/forms.py +834 -121
  144. nautobot/dcim/graphql/types.py +2 -2
  145. nautobot/dcim/homepage.py +1 -1
  146. nautobot/dcim/migrations/0059_add_role_field_to_interface_models.py +27 -0
  147. nautobot/dcim/migrations/0060_alter_cable_status_alter_consoleport__path_and_more.py +303 -0
  148. nautobot/dcim/migrations/0061_module_models.py +862 -0
  149. nautobot/dcim/migrations/0062_module_data_migration.py +25 -0
  150. nautobot/dcim/models/__init__.py +8 -0
  151. nautobot/dcim/models/cables.py +15 -0
  152. nautobot/dcim/models/device_component_templates.py +207 -53
  153. nautobot/dcim/models/device_components.py +275 -99
  154. nautobot/dcim/models/devices.py +468 -13
  155. nautobot/dcim/models/racks.py +0 -1
  156. nautobot/dcim/navigation.py +47 -0
  157. nautobot/dcim/signals.py +3 -3
  158. nautobot/dcim/tables/__init__.py +35 -23
  159. nautobot/dcim/tables/devices.py +229 -43
  160. nautobot/dcim/tables/devicetypes.py +65 -9
  161. nautobot/dcim/tables/racks.py +5 -1
  162. nautobot/dcim/tables/template_code.py +46 -26
  163. nautobot/dcim/templates/dcim/cable_connect.html +76 -3
  164. nautobot/dcim/templates/dcim/console_port_connection_list.html +7 -5
  165. nautobot/dcim/templates/dcim/device/base.html +14 -6
  166. nautobot/dcim/templates/dcim/device/consoleports.html +2 -3
  167. nautobot/dcim/templates/dcim/device/consoleserverports.html +2 -3
  168. nautobot/dcim/templates/dcim/device/devicebays.html +6 -7
  169. nautobot/dcim/templates/dcim/device/frontports.html +2 -3
  170. nautobot/dcim/templates/dcim/device/interfaces.html +2 -3
  171. nautobot/dcim/templates/dcim/device/inventory.html +2 -3
  172. nautobot/dcim/templates/dcim/device/modulebays.html +49 -0
  173. nautobot/dcim/templates/dcim/device/poweroutlets.html +2 -3
  174. nautobot/dcim/templates/dcim/device/powerports.html +2 -3
  175. nautobot/dcim/templates/dcim/device/rearports.html +2 -3
  176. nautobot/dcim/templates/dcim/device.html +45 -1
  177. nautobot/dcim/templates/dcim/device_component.html +13 -5
  178. nautobot/dcim/templates/dcim/device_list.html +2 -1
  179. nautobot/dcim/templates/dcim/devicetype.html +99 -98
  180. nautobot/dcim/templates/dcim/devicetype_list.html +8 -16
  181. nautobot/dcim/templates/dcim/inc/devicetype_component_table.html +1 -1
  182. nautobot/dcim/templates/dcim/inc/moduletype_component_table.html +39 -0
  183. nautobot/dcim/templates/dcim/interface.html +17 -2
  184. nautobot/dcim/templates/dcim/interface_connection_list.html +7 -5
  185. nautobot/dcim/templates/dcim/interface_edit.html +1 -0
  186. nautobot/dcim/templates/dcim/manufacturer.html +24 -0
  187. nautobot/dcim/templates/dcim/module/base.html +97 -0
  188. nautobot/dcim/templates/dcim/module_bulk_destroy.html +5 -0
  189. nautobot/dcim/templates/dcim/module_consoleports.html +53 -0
  190. nautobot/dcim/templates/dcim/module_consoleserverports.html +53 -0
  191. nautobot/dcim/templates/dcim/module_destroy.html +5 -0
  192. nautobot/dcim/templates/dcim/module_frontports.html +53 -0
  193. nautobot/dcim/templates/dcim/module_interfaces.html +57 -0
  194. nautobot/dcim/templates/dcim/module_list.html +20 -0
  195. nautobot/dcim/templates/dcim/module_modulebays.html +49 -0
  196. nautobot/dcim/templates/dcim/module_poweroutlets.html +53 -0
  197. nautobot/dcim/templates/dcim/module_powerports.html +53 -0
  198. nautobot/dcim/templates/dcim/module_rearports.html +53 -0
  199. nautobot/dcim/templates/dcim/module_retrieve.html +63 -0
  200. nautobot/dcim/templates/dcim/module_update.html +71 -0
  201. nautobot/dcim/templates/dcim/modulebay_bulk_destroy.html +5 -0
  202. nautobot/dcim/templates/dcim/modulebay_destroy.html +8 -0
  203. nautobot/dcim/templates/dcim/modulebay_retrieve.html +101 -0
  204. nautobot/dcim/templates/dcim/moduletype_list.html +11 -0
  205. nautobot/dcim/templates/dcim/moduletype_retrieve.html +159 -0
  206. nautobot/dcim/templates/dcim/power_port_connection_list.html +7 -5
  207. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +65 -19
  208. nautobot/dcim/tests/integration/test_cable_connect_form.py +4 -4
  209. nautobot/dcim/tests/test_api.py +693 -208
  210. nautobot/dcim/tests/test_filters.py +843 -217
  211. nautobot/dcim/tests/test_models.py +1072 -8
  212. nautobot/dcim/tests/test_views.py +1510 -341
  213. nautobot/dcim/urls.py +17 -2
  214. nautobot/dcim/utils.py +2 -3
  215. nautobot/dcim/views.py +1106 -116
  216. nautobot/extras/__init__.py +0 -1
  217. nautobot/extras/api/serializers.py +115 -3
  218. nautobot/extras/api/urls.py +12 -0
  219. nautobot/extras/api/views.py +66 -0
  220. nautobot/extras/apps.py +2 -2
  221. nautobot/extras/choices.py +43 -0
  222. nautobot/extras/context_managers.py +13 -8
  223. nautobot/extras/datasources/git.py +2 -0
  224. nautobot/extras/factory.py +460 -9
  225. nautobot/extras/filters/__init__.py +174 -3
  226. nautobot/extras/filters/mixins.py +46 -43
  227. nautobot/extras/forms/base.py +24 -5
  228. nautobot/extras/forms/forms.py +227 -8
  229. nautobot/extras/forms/mixins.py +93 -0
  230. nautobot/extras/graphql/types.py +23 -10
  231. nautobot/extras/homepage.py +14 -1
  232. nautobot/extras/management/__init__.py +1 -0
  233. nautobot/extras/management/commands/refresh_dynamic_group_member_caches.py +1 -16
  234. nautobot/extras/migrations/0021_customfield_changelog_data.py +1 -0
  235. nautobot/extras/migrations/0109_dynamicgroup_group_type_dynamicgroup_tags_and_more.py +108 -0
  236. nautobot/extras/migrations/0110_alter_configcontext_cluster_groups_and_more.py +111 -0
  237. nautobot/extras/migrations/0111_metadata.py +162 -0
  238. nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +28 -0
  239. nautobot/extras/migrations/0113_saved_views.py +77 -0
  240. nautobot/extras/models/__init__.py +15 -1
  241. nautobot/extras/models/change_logging.py +3 -3
  242. nautobot/extras/models/contacts.py +4 -0
  243. nautobot/extras/models/customfields.py +18 -3
  244. nautobot/extras/models/groups.py +389 -225
  245. nautobot/extras/models/jobs.py +6 -3
  246. nautobot/extras/models/metadata.py +441 -0
  247. nautobot/extras/models/mixins.py +72 -62
  248. nautobot/extras/models/models.py +118 -9
  249. nautobot/extras/models/relationships.py +9 -2
  250. nautobot/extras/models/tags.py +13 -2
  251. nautobot/extras/navigation.py +57 -0
  252. nautobot/extras/plugins/__init__.py +3 -1
  253. nautobot/extras/querysets.py +30 -66
  254. nautobot/extras/signals.py +95 -100
  255. nautobot/extras/tables.py +165 -12
  256. nautobot/extras/templates/extras/dynamicgroup.html +44 -15
  257. nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -0
  258. nautobot/extras/templates/extras/job.html +1 -1
  259. nautobot/extras/templates/extras/jobresult.html +61 -74
  260. nautobot/extras/templates/extras/metadatatype_create.html +89 -0
  261. nautobot/extras/templates/extras/metadatatype_retrieve.html +67 -0
  262. nautobot/extras/templates/extras/object_dynamicgroups.html +7 -0
  263. nautobot/extras/templates/extras/objectchange_list.html +0 -12
  264. nautobot/extras/templates/extras/plugins_list.html +1 -3
  265. nautobot/extras/templates/extras/role_retrieve.html +48 -0
  266. nautobot/extras/templates/extras/staticgroupassociation_retrieve.html +20 -0
  267. nautobot/extras/tests/integration/test_customfields.py +1 -0
  268. nautobot/extras/tests/test_api.py +509 -23
  269. nautobot/extras/tests/test_changelog.py +20 -9
  270. nautobot/extras/tests/test_context_managers.py +22 -15
  271. nautobot/extras/tests/test_datasources.py +13 -1
  272. nautobot/extras/tests/test_dynamicgroups.py +201 -171
  273. nautobot/extras/tests/test_filters.py +211 -12
  274. nautobot/extras/tests/test_jobs.py +6 -6
  275. nautobot/extras/tests/test_models.py +501 -4
  276. nautobot/extras/tests/test_relationships.py +1 -0
  277. nautobot/extras/tests/test_views.py +565 -8
  278. nautobot/extras/tests/test_webhooks.py +1 -1
  279. nautobot/extras/urls.py +5 -0
  280. nautobot/extras/utils.py +51 -11
  281. nautobot/extras/views.py +542 -76
  282. nautobot/ipam/__init__.py +0 -1
  283. nautobot/ipam/apps.py +1 -0
  284. nautobot/ipam/factory.py +17 -19
  285. nautobot/ipam/filters.py +13 -0
  286. nautobot/ipam/forms.py +8 -4
  287. nautobot/ipam/graphql/types.py +2 -2
  288. nautobot/ipam/migrations/0047_alter_ipaddress_role_alter_ipaddress_status_and_more.py +59 -0
  289. nautobot/ipam/models.py +11 -8
  290. nautobot/ipam/querysets.py +1 -1
  291. nautobot/ipam/signals.py +4 -2
  292. nautobot/ipam/tables.py +5 -0
  293. nautobot/ipam/templates/ipam/ipaddress_interfaces.html +1 -1
  294. nautobot/ipam/templates/ipam/ipaddress_vm_interfaces.html +1 -1
  295. nautobot/ipam/templates/ipam/prefix.html +1 -0
  296. nautobot/ipam/tests/test_api.py +37 -18
  297. nautobot/ipam/tests/test_filters.py +26 -2
  298. nautobot/ipam/tests/test_models.py +6 -0
  299. nautobot/ipam/tests/test_querysets.py +1 -1
  300. nautobot/ipam/tests/test_views.py +3 -2
  301. nautobot/ipam/urls.py +2 -2
  302. nautobot/ipam/views.py +18 -26
  303. nautobot/project-static/css/base.css +20 -0
  304. nautobot/project-static/css/dark.css +11 -0
  305. nautobot/project-static/docs/404.html +892 -88
  306. nautobot/project-static/docs/apps/index.html +892 -88
  307. nautobot/project-static/docs/apps/nautobot-apps.html +892 -88
  308. nautobot/project-static/docs/assets/_mkdocstrings.css +5 -0
  309. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css +1 -0
  310. nautobot/project-static/docs/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  311. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +919 -120
  312. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +904 -101
  313. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +1618 -903
  314. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +935 -144
  315. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +977 -188
  316. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +901 -99
  317. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +897 -93
  318. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +991 -193
  319. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +974 -131
  320. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +1078 -272
  321. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +1242 -334
  322. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +1727 -875
  323. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +1164 -381
  324. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +2088 -1374
  325. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +2246 -1422
  326. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +912 -111
  327. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +963 -163
  328. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +1010 -223
  329. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +1913 -1277
  330. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +1846 -1102
  331. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +904 -101
  332. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +2331 -1699
  333. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +1802 -1024
  334. nautobot/project-static/docs/development/apps/api/configuration-view.html +892 -88
  335. nautobot/project-static/docs/development/apps/api/database-backend-config.html +892 -88
  336. nautobot/project-static/docs/development/apps/api/models/django-admin.html +892 -88
  337. nautobot/project-static/docs/development/apps/api/models/global-search.html +892 -88
  338. nautobot/project-static/docs/development/apps/api/models/graphql.html +892 -88
  339. nautobot/project-static/docs/development/apps/api/models/index.html +942 -90
  340. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +892 -88
  341. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +892 -88
  342. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +892 -88
  343. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +892 -88
  344. nautobot/project-static/docs/development/apps/api/platform-features/index.html +892 -88
  345. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +892 -88
  346. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +892 -88
  347. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +892 -88
  348. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +892 -88
  349. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +892 -88
  350. nautobot/project-static/docs/development/apps/api/prometheus.html +892 -88
  351. nautobot/project-static/docs/development/apps/api/setup.html +892 -88
  352. nautobot/project-static/docs/development/apps/api/testing.html +892 -88
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +892 -88
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +892 -88
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +892 -88
  356. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +892 -88
  357. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +892 -88
  358. nautobot/project-static/docs/development/apps/api/views/base-template.html +892 -88
  359. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +892 -88
  360. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +892 -88
  361. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +892 -88
  362. nautobot/project-static/docs/development/apps/api/views/index.html +892 -88
  363. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +892 -88
  364. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +892 -88
  365. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +892 -88
  366. nautobot/project-static/docs/development/apps/api/views/notes.html +892 -88
  367. nautobot/project-static/docs/development/apps/api/views/rest-api.html +892 -88
  368. nautobot/project-static/docs/development/apps/api/views/urls.html +892 -88
  369. nautobot/project-static/docs/development/apps/index.html +892 -88
  370. nautobot/project-static/docs/development/apps/migration/code-updates.html +892 -88
  371. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +892 -88
  372. nautobot/project-static/docs/development/apps/migration/from-v1.html +892 -88
  373. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +892 -88
  374. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +892 -88
  375. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +892 -88
  376. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +892 -88
  377. nautobot/project-static/docs/development/apps/porting-from-netbox.html +892 -88
  378. nautobot/project-static/docs/development/core/application-registry.html +892 -88
  379. nautobot/project-static/docs/development/core/best-practices.html +893 -88
  380. nautobot/project-static/docs/development/core/bootstrap-ui.html +892 -88
  381. nautobot/project-static/docs/development/core/caching.html +892 -88
  382. nautobot/project-static/docs/development/core/controllers.html +892 -88
  383. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +892 -88
  384. nautobot/project-static/docs/development/core/generic-views.html +892 -88
  385. nautobot/project-static/docs/development/core/getting-started.html +892 -88
  386. nautobot/project-static/docs/development/core/homepage.html +892 -88
  387. nautobot/project-static/docs/development/core/index.html +892 -88
  388. nautobot/project-static/docs/development/core/model-checklist.html +901 -89
  389. nautobot/project-static/docs/development/core/model-features.html +892 -88
  390. nautobot/project-static/docs/development/core/natural-keys.html +892 -88
  391. nautobot/project-static/docs/development/core/navigation-menu.html +892 -88
  392. nautobot/project-static/docs/development/core/release-checklist.html +895 -91
  393. nautobot/project-static/docs/development/core/role-internals.html +892 -88
  394. nautobot/project-static/docs/development/core/settings.html +892 -88
  395. nautobot/project-static/docs/development/core/style-guide.html +893 -89
  396. nautobot/project-static/docs/development/core/templates.html +904 -89
  397. nautobot/project-static/docs/development/core/testing.html +892 -88
  398. nautobot/project-static/docs/development/core/user-preferences.html +892 -88
  399. nautobot/project-static/docs/development/index.html +892 -88
  400. nautobot/project-static/docs/development/jobs/index.html +893 -89
  401. nautobot/project-static/docs/development/jobs/migration/from-v1.html +892 -88
  402. nautobot/project-static/docs/index.html +892 -88
  403. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_dark.png +0 -0
  404. nautobot/project-static/docs/media/models/cloud_aws_direct_connect_light.png +0 -0
  405. nautobot/project-static/docs/models/cloud/cloudaccount.html +15 -0
  406. nautobot/project-static/docs/models/cloud/cloudnetwork.html +15 -0
  407. nautobot/project-static/docs/models/cloud/cloudnetworkprefixassignment.html +15 -0
  408. nautobot/project-static/docs/models/cloud/cloudresourcetype.html +15 -0
  409. nautobot/project-static/docs/models/cloud/cloudservice.html +15 -0
  410. nautobot/project-static/docs/models/cloud/cloudservicenetworkassignment.html +15 -0
  411. nautobot/project-static/docs/models/dcim/module.html +15 -0
  412. nautobot/project-static/docs/models/dcim/modulebay.html +15 -0
  413. nautobot/project-static/docs/models/dcim/modulebaytemplate.html +15 -0
  414. nautobot/project-static/docs/models/dcim/moduletype.html +15 -0
  415. nautobot/project-static/docs/models/extras/metadatachoice.html +15 -0
  416. nautobot/project-static/docs/models/extras/metadatatype.html +15 -0
  417. nautobot/project-static/docs/models/extras/objectmetadata.html +15 -0
  418. nautobot/project-static/docs/models/extras/role.html +15 -0
  419. nautobot/project-static/docs/models/extras/savedview.html +15 -0
  420. nautobot/project-static/docs/models/extras/staticgroupassociation.html +15 -0
  421. nautobot/project-static/docs/models/extras/status.html +15 -0
  422. nautobot/project-static/docs/objects.inv +0 -0
  423. nautobot/project-static/docs/overview/application_stack.html +900 -89
  424. nautobot/project-static/docs/overview/design_philosophy.html +892 -88
  425. nautobot/project-static/docs/release-notes/index.html +1129 -92
  426. nautobot/project-static/docs/release-notes/version-1.0.html +892 -88
  427. nautobot/project-static/docs/release-notes/version-1.1.html +892 -88
  428. nautobot/project-static/docs/release-notes/version-1.2.html +892 -88
  429. nautobot/project-static/docs/release-notes/version-1.3.html +892 -88
  430. nautobot/project-static/docs/release-notes/version-1.4.html +892 -88
  431. nautobot/project-static/docs/release-notes/version-1.5.html +893 -89
  432. nautobot/project-static/docs/release-notes/version-1.6.html +893 -89
  433. nautobot/project-static/docs/release-notes/version-2.0.html +892 -88
  434. nautobot/project-static/docs/release-notes/version-2.1.html +892 -88
  435. nautobot/project-static/docs/release-notes/version-2.2.html +895 -91
  436. nautobot/project-static/docs/release-notes/version-2.3.html +9954 -0
  437. nautobot/project-static/docs/requirements.txt +5 -5
  438. nautobot/project-static/docs/search/search_index.json +1 -1
  439. nautobot/project-static/docs/sitemap.xml +331 -256
  440. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  441. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +892 -88
  442. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +892 -88
  443. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +892 -88
  444. nautobot/project-static/docs/user-guide/administration/configuration/index.html +892 -88
  445. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +992 -174
  446. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +892 -88
  447. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +892 -88
  448. nautobot/project-static/docs/user-guide/administration/guides/caching.html +892 -88
  449. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +896 -88
  450. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +892 -88
  451. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +892 -88
  452. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +892 -88
  453. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +892 -88
  454. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +892 -88
  455. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +892 -88
  456. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +892 -88
  457. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +892 -88
  458. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +944 -153
  459. nautobot/project-static/docs/user-guide/administration/installation/index.html +901 -93
  460. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +934 -122
  461. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +954 -157
  462. nautobot/project-static/docs/user-guide/administration/installation/services.html +913 -112
  463. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +908 -99
  464. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +892 -88
  465. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +892 -88
  466. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +892 -88
  467. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +892 -88
  468. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +893 -89
  469. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +892 -88
  470. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +892 -88
  471. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +892 -88
  472. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +892 -88
  473. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +892 -88
  474. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +892 -88
  475. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +892 -88
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +892 -88
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +892 -88
  478. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +892 -88
  479. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +892 -88
  480. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +893 -89
  481. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +892 -88
  482. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +896 -88
  483. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +895 -91
  484. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +8984 -0
  485. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +8828 -0
  486. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +8829 -0
  487. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +8828 -0
  488. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +8829 -0
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +8833 -0
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +8828 -0
  491. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +906 -102
  492. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +923 -105
  493. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +923 -105
  494. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +918 -100
  495. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +923 -105
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +906 -102
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +906 -102
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +913 -105
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +920 -116
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +921 -117
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +918 -114
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +906 -102
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +914 -105
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +926 -108
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +936 -118
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +928 -106
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +906 -102
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +937 -119
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +928 -110
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +918 -114
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +921 -117
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +923 -115
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +8828 -0
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +8846 -0
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +8843 -0
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +8823 -0
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +916 -112
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +906 -102
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +940 -83
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +924 -106
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +906 -102
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +943 -86
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +921 -103
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +929 -125
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +918 -114
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +906 -102
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +922 -104
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +924 -106
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +906 -102
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +906 -102
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +906 -102
  532. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +936 -88
  533. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +892 -88
  534. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +897 -89
  535. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +897 -89
  536. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +892 -88
  537. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +892 -88
  538. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +892 -88
  539. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +892 -88
  540. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +892 -88
  541. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +892 -88
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +892 -88
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +892 -88
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +892 -88
  545. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +892 -88
  546. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +901 -96
  547. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +892 -88
  548. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +892 -88
  549. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +892 -88
  550. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +892 -88
  551. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +892 -88
  552. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +897 -89
  553. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +892 -88
  554. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +892 -88
  555. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +892 -88
  556. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +892 -88
  557. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +892 -88
  558. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +892 -88
  559. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +892 -88
  560. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +892 -88
  561. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +892 -88
  562. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +892 -88
  563. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +892 -88
  564. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +892 -88
  565. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +892 -88
  566. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/clear-view-button.png +0 -0
  567. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/cleared-view.png +0 -0
  568. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/config-table-columns-to-locations.png +0 -0
  569. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/configure-button.png +0 -0
  570. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/create-saved-view-success.png +0 -0
  571. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/current-saved-view-drop-down-menu.png +0 -0
  572. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/default-location-list-view.png +0 -0
  573. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/dropdown-button-after-new-saved-view.png +0 -0
  574. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-application-to-locations.png +0 -0
  575. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/filter-button.png +0 -0
  576. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/global-default-location-list-view.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/location-list-view-with-saved-views.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/navigation-menu.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-as-new-view-drop-down.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/save-view-modal.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-buttons.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-success.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view-unchecked.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-admin-edit-view.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-different-user.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/saved-view-modal-unchecked.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-button.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/set-as-my-default-success.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/unsaved-saved-view.png +0 -0
  590. nautobot/project-static/docs/user-guide/feature-guides/images/saved-views/updated-saved-view.png +0 -0
  591. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +892 -88
  592. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +892 -88
  593. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +892 -88
  594. nautobot/project-static/docs/user-guide/index.html +892 -88
  595. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +892 -88
  596. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +892 -88
  597. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +892 -88
  598. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +892 -88
  599. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1258 -785
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +895 -91
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +892 -88
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +892 -88
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +892 -88
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +892 -88
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +892 -88
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +892 -88
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +892 -88
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +892 -88
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +892 -88
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +896 -88
  611. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +892 -88
  612. nautobot/project-static/docs/user-guide/platform-functionality/note.html +895 -91
  613. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +9061 -0
  614. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +895 -91
  615. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +892 -88
  616. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +892 -88
  617. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +892 -88
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +892 -88
  619. nautobot/project-static/docs/user-guide/platform-functionality/role.html +895 -91
  620. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9137 -0
  621. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +895 -91
  622. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +8933 -0
  623. nautobot/project-static/docs/user-guide/platform-functionality/status.html +892 -88
  624. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +892 -88
  625. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +950 -121
  626. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +892 -88
  627. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +892 -88
  628. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +892 -88
  629. nautobot/project-static/js/forms.js +71 -0
  630. nautobot/project-static/js/table_sorting_indicator.js +46 -0
  631. nautobot/project-static/js/tableconfig.js +6 -1
  632. nautobot/project-static/materialdesignicons-7.4.47/css/materialdesignicons.min.css +3 -0
  633. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.eot +0 -0
  634. nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/fonts/materialdesignicons-webfont.ttf +0 -0
  635. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff +0 -0
  636. nautobot/project-static/materialdesignicons-7.4.47/fonts/materialdesignicons-webfont.woff2 +0 -0
  637. nautobot/tenancy/__init__.py +0 -1
  638. nautobot/tenancy/apps.py +1 -0
  639. nautobot/tenancy/factory.py +3 -2
  640. nautobot/tenancy/filters/__init__.py +1 -0
  641. nautobot/tenancy/forms.py +1 -1
  642. nautobot/tenancy/templates/tenancy/tenant.html +24 -20
  643. nautobot/tenancy/views.py +11 -10
  644. nautobot/users/__init__.py +0 -1
  645. nautobot/users/api/serializers.py +1 -1
  646. nautobot/users/api/views.py +4 -2
  647. nautobot/users/apps.py +3 -2
  648. nautobot/users/factory.py +3 -3
  649. nautobot/users/migrations/0010_user_default_saved_views.py +20 -0
  650. nautobot/users/models.py +12 -0
  651. nautobot/users/tests/test_filters.py +6 -3
  652. nautobot/users/urls.py +8 -0
  653. nautobot/virtualization/__init__.py +0 -1
  654. nautobot/virtualization/apps.py +1 -0
  655. nautobot/virtualization/filters.py +6 -1
  656. nautobot/virtualization/forms.py +11 -3
  657. nautobot/virtualization/graphql/types.py +2 -2
  658. nautobot/virtualization/migrations/0029_add_role_field_to_interface_models.py +27 -0
  659. nautobot/virtualization/migrations/0030_alter_virtualmachine_local_config_context_data_owner_content_type_and_more.py +67 -0
  660. nautobot/virtualization/models.py +0 -2
  661. nautobot/virtualization/tables.py +10 -3
  662. nautobot/virtualization/templates/virtualization/virtualmachine.html +1 -1
  663. nautobot/virtualization/templates/virtualization/vminterface.html +7 -1
  664. nautobot/virtualization/templates/virtualization/vminterface_edit.html +1 -0
  665. nautobot/virtualization/tests/test_api.py +9 -4
  666. nautobot/virtualization/tests/test_filters.py +22 -0
  667. nautobot/virtualization/tests/test_models.py +7 -3
  668. nautobot/virtualization/tests/test_views.py +19 -3
  669. nautobot/virtualization/urls.py +2 -2
  670. nautobot/virtualization/views.py +10 -32
  671. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/METADATA +20 -18
  672. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/RECORD +677 -557
  673. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css +0 -1
  674. nautobot/project-static/docs/assets/stylesheets/main.76a95c52.min.css.map +0 -1
  675. nautobot/project-static/materialdesignicons-6.5.95/.github/ISSUE_TEMPLATE.md +0 -3
  676. nautobot/project-static/materialdesignicons-6.5.95/README.md +0 -25
  677. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css +0 -26654
  678. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.css.map +0 -16
  679. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css +0 -3
  680. nautobot/project-static/materialdesignicons-6.5.95/css/materialdesignicons.min.css.map +0 -16
  681. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff +0 -0
  682. nautobot/project-static/materialdesignicons-6.5.95/fonts/materialdesignicons-webfont.woff2 +0 -0
  683. nautobot/project-static/materialdesignicons-6.5.95/package.json +0 -28
  684. nautobot/project-static/materialdesignicons-6.5.95/preview.html +0 -717
  685. nautobot/project-static/materialdesignicons-6.5.95/scss/_animated.scss +0 -27
  686. nautobot/project-static/materialdesignicons-6.5.95/scss/_core.scss +0 -10
  687. nautobot/project-static/materialdesignicons-6.5.95/scss/_extras.scss +0 -65
  688. nautobot/project-static/materialdesignicons-6.5.95/scss/_functions.scss +0 -20
  689. nautobot/project-static/materialdesignicons-6.5.95/scss/_icons.scss +0 -10
  690. nautobot/project-static/materialdesignicons-6.5.95/scss/_path.scss +0 -10
  691. nautobot/project-static/materialdesignicons-6.5.95/scss/_variables.scss +0 -6606
  692. nautobot/project-static/materialdesignicons-6.5.95/scss/materialdesignicons.scss +0 -8
  693. /nautobot/project-static/{materialdesignicons-6.5.95 → materialdesignicons-7.4.47}/LICENSE +0 -0
  694. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/LICENSE.txt +0 -0
  695. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/NOTICE +0 -0
  696. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/WHEEL +0 -0
  697. {nautobot-2.2.9.dist-info → nautobot-2.3.0.dist-info}/entry_points.txt +0 -0
@@ -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."""