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
@@ -1,17 +1,14 @@
1
1
  import random
2
- import time
3
- from unittest.mock import patch
4
2
 
5
3
  from django.contrib.contenttypes.models import ContentType
6
- from django.core.cache import cache
7
4
  from django.core.exceptions import ValidationError
8
- from django.db.models import ProtectedError
9
- from django.test import override_settings
5
+ from django.db.models import ProtectedError, QuerySet
10
6
  from django.urls import reverse
11
7
 
12
8
  from nautobot.core.forms.fields import MultiMatchModelMultipleChoiceField, MultiValueCharField
13
9
  from nautobot.core.forms.widgets import APISelectMultiple, MultiValueCharInput
14
10
  from nautobot.core.testing import TestCase
11
+ from nautobot.core.testing.filters import FilterTestCases
15
12
  from nautobot.dcim.choices import PortTypeChoices
16
13
  from nautobot.dcim.filters import DeviceFilterSet
17
14
  from nautobot.dcim.forms import DeviceFilterForm, DeviceForm
@@ -29,6 +26,7 @@ from nautobot.extras.choices import (
29
26
  CustomFieldFilterLogicChoices,
30
27
  CustomFieldTypeChoices,
31
28
  DynamicGroupOperatorChoices,
29
+ DynamicGroupTypeChoices,
32
30
  RelationshipTypeChoices,
33
31
  )
34
32
  from nautobot.extras.filters import DynamicGroupFilterSet, DynamicGroupMembershipFilterSet
@@ -42,8 +40,10 @@ from nautobot.extras.models import (
42
40
  Status,
43
41
  Tag,
44
42
  )
45
- from nautobot.ipam.models import Prefix
43
+ from nautobot.ipam.models import IPAddress, Prefix
44
+ from nautobot.ipam.querysets import PrefixQuerySet
46
45
  from nautobot.tenancy.models import Tenant
46
+ from nautobot.virtualization.models import VirtualMachine
47
47
 
48
48
 
49
49
  class DynamicGroupTestBase(TestCase):
@@ -64,7 +64,9 @@ class DynamicGroupTestBase(TestCase):
64
64
  ]
65
65
 
66
66
  cls.manufacturer = Manufacturer.objects.first()
67
- cls.device_type = DeviceType.objects.first()
67
+ cls.device_type = DeviceType.objects.create(
68
+ manufacturer=cls.manufacturer, model="Test Dynamic Groups Device Type"
69
+ )
68
70
  cls.device_role = Role.objects.get_for_model(Device).first()
69
71
  statuses = Status.objects.get_for_model(Device)
70
72
  cls.status_1 = statuses[0]
@@ -107,7 +109,7 @@ class DynamicGroupTestBase(TestCase):
107
109
  DynamicGroup.objects.create(
108
110
  name="Parent",
109
111
  description="The parent group with no filter",
110
- filter={},
112
+ group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_SET,
111
113
  content_type=cls.device_ct,
112
114
  ),
113
115
  # Location-1 only
@@ -128,7 +130,7 @@ class DynamicGroupTestBase(TestCase):
128
130
  DynamicGroup.objects.create(
129
131
  name="Third Child",
130
132
  description="A third child group with a child of its own",
131
- filter={},
133
+ group_type=DynamicGroupTypeChoices.TYPE_DYNAMIC_SET,
132
134
  content_type=cls.device_ct,
133
135
  ),
134
136
  # Nested child of third-child to test ancestors/descendants
@@ -167,6 +169,7 @@ class DynamicGroupTestBase(TestCase):
167
169
  filter={"platform": ["invalidvalue"]},
168
170
  content_type=cls.device_ct,
169
171
  )
172
+ cls.groups.append(cls.invalid_filter)
170
173
 
171
174
  # Setup the group membership hiearchy to use for graph testing
172
175
  cls.memberships = [
@@ -196,13 +199,6 @@ class DynamicGroupTestBase(TestCase):
196
199
  ),
197
200
  ]
198
201
 
199
- def assertQuerySetEqual(self, left_qs, right_qs):
200
- """Compare two querysets and assert that they are equal."""
201
- self.assertEqual(
202
- sorted(left_qs.values_list("pk", flat=True)),
203
- sorted(right_qs.values_list("pk", flat=True)),
204
- )
205
-
206
202
 
207
203
  class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mixin?
208
204
  """DynamicGroup model tests."""
@@ -250,19 +246,22 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
250
246
 
251
247
  def test_full_clean_valid(self):
252
248
  """Test a clean validation."""
253
- group = self.groups[0]
254
- group.refresh_from_db()
255
- old_filter = group.filter
249
+ set_group = self.groups[0]
250
+ filter_group = self.groups[1]
251
+ old_filter = filter_group.filter
256
252
 
257
253
  # Overload the filter and validate that it is the same afterward.
258
254
  new_filter = {"has_interfaces": True}
259
- group.set_filter(new_filter)
260
- group.validated_save()
261
- self.assertEqual(group.filter, new_filter)
255
+ with self.assertRaises(ValidationError):
256
+ set_group.set_filter(new_filter)
257
+ set_group.validated_save()
258
+ filter_group.set_filter(new_filter)
259
+ filter_group.validated_save()
260
+ self.assertEqual(filter_group.filter, new_filter)
262
261
 
263
262
  # Restore the old filter.
264
- group.filter = old_filter
265
- group.save()
263
+ filter_group.filter = old_filter
264
+ filter_group.save()
266
265
 
267
266
  def test_get_for_object(self):
268
267
  """Test `DynamicGroup.objects.get_for_object()`."""
@@ -272,12 +271,12 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
272
271
  # Assert that the groups we got from `get_for_object()` match the lookup
273
272
  # from the group instance itself.
274
273
  device1_groups = DynamicGroup.objects.get_for_object(device1)
275
- self.assertQuerySetEqual(device1_groups, device1.dynamic_groups)
274
+ self.assertQuerysetEqualAndNotEmpty(device1_groups, device1.dynamic_groups)
276
275
 
277
276
  # Device4 should not be in ANY Dynamic Groups.
278
277
  device4_groups = DynamicGroup.objects.get_for_object(device4)
279
278
  self.assertEqual(list(device4_groups), [])
280
- self.assertQuerySetEqual(device4_groups, device4.dynamic_groups)
279
+ self.assertQuerysetEqual(device4.dynamic_groups, [])
281
280
 
282
281
  def test_members(self):
283
282
  """Test `DynamicGroup.members`."""
@@ -288,6 +287,50 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
288
287
  self.assertIn(device1, group.members)
289
288
  self.assertNotIn(device2, group.members)
290
289
 
290
+ def test_static_member_operations(self):
291
+ sg = DynamicGroup.objects.create(
292
+ name="All Prefixes",
293
+ content_type=ContentType.objects.get_for_model(Prefix),
294
+ group_type=DynamicGroupTypeChoices.TYPE_STATIC,
295
+ )
296
+ self.assertIsInstance(sg.members, PrefixQuerySet)
297
+ self.assertEqual(sg.members.count(), 0)
298
+ # test type validation
299
+ with self.assertRaises(TypeError):
300
+ sg.add_members([IPAddress.objects.first()])
301
+ # test bulk addition
302
+ sg.add_members(Prefix.objects.filter(ip_version=4))
303
+ self.assertIsInstance(sg.members, PrefixQuerySet)
304
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
305
+ # test duplicate objects aren't re-added
306
+ sg.add_members(Prefix.objects.all())
307
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
308
+ self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
309
+ # test idempotence and alternate code path
310
+ sg.add_members(list(Prefix.objects.all()))
311
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.all())
312
+ self.assertEqual(sg.static_group_associations.count(), Prefix.objects.all().count())
313
+
314
+ # test bulk removal
315
+ sg.remove_members(Prefix.objects.filter(ip_version=4))
316
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
317
+ self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
318
+ # test idempotence and alternate code path
319
+ sg.remove_members(list(Prefix.objects.filter(ip_version=4)))
320
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
321
+ self.assertEqual(sg.static_group_associations.count(), Prefix.objects.filter(ip_version=6).count())
322
+
323
+ # test property setter
324
+ sg.members = Prefix.objects.filter(ip_version=4)
325
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=4))
326
+ sg.members = list(Prefix.objects.filter(ip_version=6))
327
+ self.assertQuerysetEqualAndNotEmpty(sg.members, Prefix.objects.filter(ip_version=6))
328
+
329
+ self.assertIsInstance(Prefix.objects.filter(ip_version=6).first().dynamic_groups, QuerySet)
330
+ self.assertIn(sg, list(Prefix.objects.filter(ip_version=6).first().dynamic_groups))
331
+
332
+ # TODO negative test that members=, add_members(), remove_members() raise appropriate errors for non-static groups
333
+
291
334
  def test_members_fail_closed(self):
292
335
  """An invalid filter should fail closed, not fail open."""
293
336
  self.assertFalse(self.invalid_filter.members.exists())
@@ -336,8 +379,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
336
379
  expected,
337
380
  )
338
381
 
339
- # Now also test that an advancted (nested) dynamic group, also reports
340
- # the same number of members.
382
+ # Now also test that an advanced (nested) dynamic group also reports the same number of members.
341
383
  parent_group = DynamicGroup.objects.create(
342
384
  name="Parent of Devices Location",
343
385
  content_type=self.device_ct,
@@ -353,6 +395,32 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
353
395
  expected,
354
396
  )
355
397
 
398
+ def test_has_member(self):
399
+ """Test `DynamicGroup.has_member()`."""
400
+ group = self.first_child
401
+ device1 = self.devices[0]
402
+ device2 = self.devices[1]
403
+
404
+ with self.assertApproximateNumQueries(minimum=1, maximum=10):
405
+ group.update_cached_members()
406
+ with self.assertNumQueries(1):
407
+ self.assertTrue(group.has_member(device1))
408
+ with self.assertNumQueries(1):
409
+ self.assertFalse(group.has_member(device2))
410
+ # Test idempotence
411
+ group.update_cached_members()
412
+
413
+ # Test fail-closed behavior of an invalid group filter
414
+ group = self.invalid_filter
415
+ with self.assertApproximateNumQueries(minimum=1, maximum=5):
416
+ group.update_cached_members()
417
+ with self.assertNumQueries(1):
418
+ self.assertFalse(group.has_member(device1))
419
+ with self.assertNumQueries(1):
420
+ self.assertFalse(group.has_member(device2))
421
+ # Test idempotence
422
+ group.update_cached_members()
423
+
356
424
  def test_count(self):
357
425
  """Test `DynamicGroup.count`."""
358
426
  expected = {
@@ -367,29 +435,6 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
367
435
  for grp, cnt in expected.items():
368
436
  self.assertEqual(grp, cnt)
369
437
 
370
- def test_get_queryset(self):
371
- """Test `DynamicGroup.get_queryset()`."""
372
- group = self.first_child
373
- device1 = self.devices[0]
374
-
375
- # Test that we can get a full queryset
376
- qs = group.get_queryset()
377
- devices = group.model.objects.filter(location=device1.location)
378
-
379
- # Expect a single-member qs/list of Device names (only `device1`)
380
- expected = [device1.name]
381
- self.assertIn(device1, devices)
382
- self.assertIn(device1, qs)
383
- self.assertEqual(list(map(str, devices)), expected)
384
- self.assertEqual(list(map(str, qs)), expected)
385
- self.assertEqual(list(qs), list(devices))
386
-
387
- # A new group that doesn't have a content_type and therefore
388
- # `self.model`, should raise a RuntimeError
389
- new_group = DynamicGroup()
390
- with self.assertRaises(RuntimeError):
391
- new_group.get_queryset()
392
-
393
438
  def test_model(self):
394
439
  """Test `DynamicGroup.model`."""
395
440
  # New instances should not have a model unless `content_type` is set.
@@ -400,19 +445,18 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
400
445
  new_group.content_type = self.device_ct
401
446
  self.assertIsNotNone(new_group.model)
402
447
 
403
- def test_set_object_classes(self):
404
- """Test `DynamicGroup._set_object_classes()`."""
448
+ def test_object_classes(self):
449
+ """Test `DynamicGroup object_class dynamic population."""
405
450
  # New instances should fail to map until `content_type` is set.
406
451
  new_group = DynamicGroup(name="Unsaved Group")
407
- objects_mapped = new_group._set_object_classes(new_group.model)
408
- self.assertFalse(objects_mapped)
452
+ self.assertIsNone(new_group.filterset_class)
453
+ self.assertIsNone(new_group.filterform_class)
454
+ self.assertIsNone(new_group.form_class)
409
455
 
410
456
  # Existing groups w/ `content_type` set work as expected.
411
457
  group = self.groups[0]
412
458
  model = group.content_type.model_class()
413
- objects_mapped = group._set_object_classes(model)
414
459
 
415
- self.assertTrue(objects_mapped)
416
460
  self.assertEqual(group.model, model)
417
461
  self.assertEqual(group.filterset_class, DeviceFilterSet)
418
462
  self.assertEqual(group.filterform_class, DeviceFilterForm)
@@ -573,7 +617,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
573
617
 
574
618
  def test_set_filter(self):
575
619
  """Test `DynamicGroup.set_filter()`."""
576
- group = self.groups[0]
620
+ group = self.first_child
577
621
 
578
622
  # Input can come from a form's cleaned_data, such as our generated form. In this case, the
579
623
  # filter we set from the form should be identical to what was there already.
@@ -611,6 +655,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
611
655
  """Test various ways in which adding a child group should fail."""
612
656
  parent = self.parent
613
657
  parent.filter = {"location": ["Location 1"]}
658
+ parent.group_type = DynamicGroupTypeChoices.TYPE_DYNAMIC_FILTER
614
659
  child = self.no_match_filter
615
660
 
616
661
  # parent.add_child() should fail
@@ -634,36 +679,36 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
634
679
  self.assertFalse(self.parent.children.filter(name=self.third_child.name).exists())
635
680
 
636
681
  def test_generate_query_for_filter(self):
637
- """Test `DynamicGroup.generate_query_for_filter()`."""
638
- group = self.parent # Any group will do, so why not this one?
682
+ """Test `DynamicGroup._generate_query_for_filter()`."""
683
+ group = self.first_child # Any filter-based group will do, so why not this one?
639
684
  multi_value = ["Location 3"]
640
685
  fs = group.filterset_class()
641
686
  multi_field = fs.filters["location"]
642
- multi_query = group.generate_query_for_filter(
687
+ multi_query = group._generate_query_for_filter(
643
688
  filter_field=multi_field,
644
689
  value=multi_value,
645
690
  )
646
691
 
647
- queryset = group.get_queryset()
692
+ queryset = group.model.objects.all()
648
693
 
649
694
  # Assert that both querysets return the same results
650
695
  group_qs = queryset.filter(multi_query)
651
696
  device_qs = Device.objects.filter(location__name__in=multi_value)
652
- self.assertQuerySetEqual(group_qs, device_qs)
697
+ self.assertQuerysetEqual(group_qs, device_qs, ordered=False)
653
698
 
654
699
  # Now do a non-multi-value filter.
655
700
  solo_field = fs.filters["has_interfaces"]
656
701
  solo_value = False
657
- solo_query = group.generate_query_for_filter(filter_field=solo_field, value=solo_value)
702
+ solo_query = group._generate_query_for_filter(filter_field=solo_field, value=solo_value)
658
703
  solo_qs = queryset.filter(solo_query)
659
704
  interface_qs = Device.objects.filter(interfaces__isnull=True)
660
- self.assertQuerySetEqual(solo_qs, interface_qs)
705
+ self.assertQuerysetEqual(solo_qs, interface_qs, ordered=False)
661
706
 
662
707
  # Tags are conjoined in the TagFilterSet, ensure that tags__name is using AND. We know this isn't right
663
708
  # since the resulting query actually does tag.name == tag_1 AND tag.name == tag_2, but django_filter does
664
709
  # not use Q evaluation for conjoined filters. This function is only used for the display, and the display
665
710
  # is good enough to get the point across.
666
- tags_query = group.generate_query_for_filter(filter_field=fs.filters["tags"], value=["tag_1", "tag_2"])
711
+ tags_query = group._generate_query_for_filter(filter_field=fs.filters["tags"], value=["tag_1", "tag_2"])
667
712
  self.assertEqual(str(tags_query), "(AND: ('tags__name', 'tag_1'), ('tags__name', 'tag_2'))")
668
713
 
669
714
  # Test that a nested field_name w/ `generate_query` works as expected. This is explicitly to
@@ -677,39 +722,32 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
677
722
  # We are making sure the filterset generated from the name as an argument results in the same
678
723
  # filtered queryset, and more importantly that the nested filter expression `device_type__manufacturer`
679
724
  # is automatically used to get the related model name without failing.
680
- nested_query = group.generate_query_for_filter(filter_field=fs.filters["manufacturer"], value=nested_value)
725
+ nested_query = group._generate_query_for_filter(filter_field=fs.filters["manufacturer"], value=nested_value)
681
726
  nested_qs = queryset.filter(nested_query)
682
727
  parent_qs = Device.objects.filter(device_type__manufacturer__name__in=nested_value)
683
- self.assertQuerySetEqual(nested_qs, parent_qs)
728
+ self.assertQuerysetEqual(nested_qs, parent_qs, ordered=False)
684
729
 
685
- def test_generate_query_for_group(self):
686
- """Test `DynamicGroup.generate_query_for_group()`."""
687
- group = self.parent
688
-
689
- # A group with an empty filter will have a null `Q` object
690
- parent_q = group.generate_query_for_group(group)
691
- self.assertFalse(parent_q)
692
-
693
- # A child group with a filter set will result in a useful Q object.
694
- child_q = group.generate_query_for_group(self.second_child)
730
+ def test_generate_filter_based_query(self):
731
+ """Test `DynamicGroup._generate_filter_based_query()`."""
732
+ # A group with a filter set will result in a useful Q object.
733
+ child_q = self.second_child._generate_filter_based_query()
695
734
  lookup_kwargs = dict(child_q.children) # {name: value}
696
735
 
697
- # Assert that both querysets resturn the same results
698
- group_qs = group.get_queryset().filter(child_q)
736
+ # Assert that both querysets return the same results
737
+ group_qs = self.second_child.members
699
738
  device_qs = Device.objects.filter(**lookup_kwargs)
700
- self.assertQuerySetEqual(group_qs, device_qs)
739
+ self.assertQuerysetEqual(group_qs, device_qs, ordered=False)
701
740
 
702
741
  def test_get_group_queryset(self):
703
- """Test `DynamicGroup.get_group_queryset()`."""
704
- # This is literally just calling `process_group_filters(self)` so let's
705
- # just make sure that it stays consistent until we decide otherwise.
742
+ """Test `DynamicGroup._get_group_queryset()`."""
706
743
  group = self.parent
707
- group_qs = group.get_group_queryset()
744
+ group_qs = group._get_group_queryset()
745
+
708
746
  process_query = group.generate_query()
709
- base_qs = group.get_queryset()
747
+ base_qs = Device.objects.all()
710
748
  process_qs = base_qs.filter(process_query)
711
749
 
712
- self.assertQuerySetEqual(group_qs, process_qs)
750
+ self.assertQuerysetEqual(group_qs, process_qs, ordered=False)
713
751
 
714
752
  def test_get_ancestors(self):
715
753
  """Test `DynamicGroup.get_ancestors()`."""
@@ -824,7 +862,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
824
862
  # Start with parent. Enumerate descendants and their operators to assert correct results.
825
863
  group = self.parent
826
864
  group_query = group.generate_query()
827
- group_qs = group.get_queryset().filter(group_query)
865
+ group_qs = Device.objects.filter(group_query)
828
866
 
829
867
  # <DynamicGroupMembership: First Child: intersection (10)>
830
868
  # <DynamicGroupMembership: Second Child: union (20)>
@@ -834,7 +872,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
834
872
  # Manually iterate over the groups to assert the same result that the queryset should have,
835
873
  # igoring weight since each set of members are already ordered by weight when queried from
836
874
  # the database.
837
- child_set = set(group.get_queryset())
875
+ child_set = set(Device.objects.all())
838
876
  for member in group_members:
839
877
  child_members = set(member.members)
840
878
  operator = member.operator
@@ -870,6 +908,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
870
908
 
871
909
  device = self.devices[0]
872
910
  prefix = Prefix.objects.first()
911
+ self.assertIsNotNone(prefix)
873
912
 
874
913
  relationship = Relationship(
875
914
  label="Device to Prefix",
@@ -984,7 +1023,7 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
984
1023
  """
985
1024
  Test that tags without being applied to any member instances can still be added as filters on DynamicGroups
986
1025
  """
987
- dg = self.groups[0]
1026
+ dg = self.first_child
988
1027
  unapplied_tag = Tag.objects.create(name="Unapplied Tag")
989
1028
  unapplied_tag.content_types.set([ContentType.objects.get_for_model(Device)])
990
1029
  unapplied_tag.save()
@@ -994,58 +1033,10 @@ class DynamicGroupModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mi
994
1033
  def test_member_caching_output(self):
995
1034
  group = self.first_child
996
1035
 
997
- # Ensure the cache is empty from previous tests
998
- cache.delete(group.members_cache_key)
999
-
1036
+ updated_members = group.update_cached_members()
1037
+ self.assertEqual(sorted(list(group.members)), sorted(list(updated_members)))
1000
1038
  self.assertEqual(sorted(list(group.members)), sorted(list(group.members_cached)))
1001
1039
 
1002
- @override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=2)
1003
- def test_member_caching_enabled(self):
1004
- """
1005
- Verify that the members list of the DynamicGroup is cached and expires.
1006
- """
1007
- group = self.first_child
1008
-
1009
- class FakeQuerySet:
1010
- def all(self):
1011
- return []
1012
-
1013
- # Ensure the cache is empty from previous tests
1014
- cache.delete(group.members_cache_key)
1015
-
1016
- with patch.object(group, "get_queryset", return_value=FakeQuerySet()) as mock_get_queryset:
1017
- group.members_cached
1018
- group.members_cached
1019
- group.members_cached
1020
- self.assertEqual(mock_get_queryset.call_count, 1)
1021
-
1022
- time.sleep(5) # Let the cache expire
1023
-
1024
- group.members_cached
1025
- self.assertEqual(mock_get_queryset.call_count, 2)
1026
-
1027
- # Clean-up after ourselves
1028
- cache.delete(group.members_cache_key)
1029
-
1030
- @override_settings(DYNAMIC_GROUPS_MEMBER_CACHE_TIMEOUT=0)
1031
- def test_member_caching_disabled(self):
1032
- """
1033
- Verify that the members list of the DynamicGroup is not cached.
1034
- """
1035
- group = self.first_child
1036
-
1037
- class FakeQuerySet:
1038
- def all(self):
1039
- return []
1040
-
1041
- # Ensure the cache is empty from previous tests
1042
- cache.delete(group.members_cache_key)
1043
-
1044
- with patch.object(group, "get_queryset", return_value=FakeQuerySet()) as mock_get_queryset:
1045
- group.members_cached
1046
- group.members_cached
1047
- self.assertEqual(mock_get_queryset.call_count, 2)
1048
-
1049
1040
 
1050
1041
  class DynamicGroupMembershipModelTest(DynamicGroupTestBase): # TODO: BaseModelTestCase mixin?
1051
1042
  """DynamicGroupMembership model tests."""
@@ -1112,60 +1103,99 @@ class DynamicGroupMembershipModelTest(DynamicGroupTestBase): # TODO: BaseModelT
1112
1103
  self.assertEqual(mem.get_group_members_url(), grp.get_group_members_url())
1113
1104
 
1114
1105
 
1115
- class DynamicGroupFilterTest(DynamicGroupTestBase):
1106
+ class DynamicGroupMixinModelTest(DynamicGroupTestBase):
1107
+ """DynamicGroupMixin model tests."""
1108
+
1109
+ def test_dynamic_groups(self):
1110
+ with self.assertApproximateNumQueries(minimum=len(self.groups), maximum=10 * len(self.groups)):
1111
+ for group in self.groups:
1112
+ group.update_cached_members()
1113
+ with self.assertNumQueries(1):
1114
+ qs = self.devices[0].dynamic_groups
1115
+ list(qs)
1116
+ self.assertQuerysetEqualAndNotEmpty(qs, [self.first_child, self.third_child, self.nested_child], ordered=False)
1117
+
1118
+ def test_dynamic_groups_cached(self):
1119
+ for group in self.groups:
1120
+ group.update_cached_members()
1121
+ with self.assertNumQueries(1):
1122
+ qs = self.devices[0].dynamic_groups_cached
1123
+ list(qs)
1124
+ self.assertQuerysetEqualAndNotEmpty(qs, [self.first_child, self.third_child, self.nested_child], ordered=False)
1125
+
1126
+ def test_dynamic_groups_list(self):
1127
+ for group in self.groups:
1128
+ group.update_cached_members()
1129
+ with self.assertNumQueries(1):
1130
+ groups = self.devices[0].dynamic_groups_list
1131
+ self.assertEqual(set(groups), set([self.first_child, self.third_child, self.nested_child]))
1132
+
1133
+ def test_dynamic_groups_list_cached(self):
1134
+ for group in self.groups:
1135
+ group.update_cached_members()
1136
+ with self.assertNumQueries(1):
1137
+ groups = self.devices[0].dynamic_groups_list_cached
1138
+ self.assertEqual(set(groups), set([self.first_child, self.third_child, self.nested_child]))
1139
+
1140
+
1141
+ class DynamicGroupFilterTest(DynamicGroupTestBase, FilterTestCases.FilterTestCase):
1116
1142
  """DynamicGroup instance filterset tests."""
1117
1143
 
1118
1144
  queryset = DynamicGroup.objects.all()
1119
1145
  filterset = DynamicGroupFilterSet
1120
-
1121
- def test_id(self):
1122
- params = {"id": self.queryset.values_list("pk", flat=True)[:2]}
1123
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1124
-
1125
- def test_name(self):
1126
- params = {"name": ["First Child", "Third Child"]}
1127
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1146
+ generic_filter_tests = (
1147
+ ["name"],
1148
+ ["description"],
1149
+ ["group_type"],
1150
+ ["tenant", "tenant__name"],
1151
+ )
1128
1152
 
1129
1153
  def test_content_type(self):
1130
1154
  params = {"content_type": ["dcim.device", "virtualization.virtualmachine"]}
1131
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
1155
+ self.assertQuerysetEqualAndNotEmpty(
1156
+ self.filterset(params, self.queryset).qs,
1157
+ DynamicGroup.objects.filter(
1158
+ content_type__in=[
1159
+ ContentType.objects.get_for_model(Device),
1160
+ ContentType.objects.get_for_model(VirtualMachine),
1161
+ ]
1162
+ ),
1163
+ )
1132
1164
 
1133
1165
  def test_search(self):
1134
1166
  tests = {
1135
1167
  "Devices No Filter": 0, # name
1136
1168
  "Invalid Filter": 1, # name
1137
1169
  "A group with a non-matching filter": 1, # description
1138
- "dcim": 8, # content_type__app_label
1139
- "device": 8, # content_type__model
1140
1170
  }
1141
1171
  for value, cnt in tests.items():
1142
1172
  params = {"q": value}
1143
1173
  self.assertEqual(self.filterset(params, self.queryset).qs.count(), cnt)
1144
1174
 
1175
+ self.assertQuerysetEqualAndNotEmpty(
1176
+ self.filterset({"q": "dcim"}).qs,
1177
+ DynamicGroup.objects.filter(content_type__app_label="dcim"),
1178
+ )
1179
+ self.assertQuerysetEqualAndNotEmpty(
1180
+ self.filterset({"q": "device"}).qs,
1181
+ DynamicGroup.objects.filter(content_type__model__icontains="device"),
1182
+ )
1145
1183
 
1146
- class DynamicGroupMembershipFilterTest(DynamicGroupTestBase):
1184
+
1185
+ class DynamicGroupMembershipFilterTest(DynamicGroupTestBase, FilterTestCases.FilterTestCase):
1147
1186
  """DynamicGroupMembership instance filterset tests."""
1148
1187
 
1149
1188
  queryset = DynamicGroupMembership.objects.all()
1150
1189
  filterset = DynamicGroupMembershipFilterSet
1151
-
1152
- def test_id(self):
1153
- params = {"id": self.queryset.values_list("pk", flat=True)[:2]}
1154
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1155
-
1156
- def test_operator(self):
1157
- params = {"operator": [DynamicGroupOperatorChoices.OPERATOR_INTERSECTION]}
1158
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1159
-
1160
- def test_weight(self):
1161
- params = {"weight": [10]}
1162
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1163
-
1164
- def test_group(self):
1165
- group_pk = self.queryset.first().group.pk # expecting 1
1166
- group_name = self.queryset.last().group.name # expecting 1
1167
- params = {"group": [group_pk, group_name]}
1168
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
1190
+ generic_filter_tests = (
1191
+ ["operator"],
1192
+ ["weight"],
1193
+ ["group", "group__id"],
1194
+ ["group", "group__name"],
1195
+ # ["parent_group", "parent_group__id"], # would work but we only have 2 valid parent groups
1196
+ # ["parent_group", "parent_group__name"], # would work but we only have 2 valid parent groups
1197
+ )
1198
+ exclude_q_filter_predicates = ["operator"]
1169
1199
 
1170
1200
  def test_parent_group(self):
1171
1201
  parent_group_pk = self.queryset.first().parent_group.pk # expecting 3