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
@@ -43,7 +43,7 @@ from nautobot.extras.constants import (
43
43
  )
44
44
  from nautobot.extras.managers import JobResultManager, ScheduledJobsManager
45
45
  from nautobot.extras.models import ChangeLoggedModel, GitRepository
46
- from nautobot.extras.models.mixins import NotesMixin
46
+ from nautobot.extras.models.mixins import ContactMixin, DynamicGroupsModelMixin, NotesMixin
47
47
  from nautobot.extras.querysets import JobQuerySet, ScheduledJobExtendedQuerySet
48
48
  from nautobot.extras.utils import (
49
49
  ChangeLoggedModelsQuery,
@@ -456,6 +456,8 @@ class JobLogEntry(BaseModel):
456
456
  log_object = models.CharField(max_length=JOB_LOG_MAX_LOG_OBJECT_LENGTH, blank=True, default="")
457
457
  absolute_url = models.CharField(max_length=JOB_LOG_MAX_ABSOLUTE_URL_LENGTH, blank=True, default="")
458
458
 
459
+ is_metadata_associable_model = False
460
+
459
461
  documentation_static_path = "docs/user-guide/platform-functionality/jobs/models.html"
460
462
 
461
463
  def __str__(self):
@@ -793,9 +795,10 @@ class JobResult(BaseModel, CustomFieldModel):
793
795
 
794
796
 
795
797
  @extras_features("graphql")
796
- class JobButton(BaseModel, ChangeLoggedModel, NotesMixin):
798
+ class JobButton(ContactMixin, ChangeLoggedModel, DynamicGroupsModelMixin, NotesMixin, BaseModel):
797
799
  """
798
- A predefined button that includes all necessary information to run a Nautobot Job based on a single object as a source.
800
+ A predefined button that includes all information to run a Nautobot Job based on a single object as a source.
801
+
799
802
  The button text field accepts Jinja2 template code to be rendered with an object as context.
800
803
  """
801
804
 
@@ -0,0 +1,441 @@
1
+ from datetime import date, datetime, timezone
2
+
3
+ from django.contrib.contenttypes.fields import GenericForeignKey
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from django.core.cache import cache
6
+ from django.core.exceptions import ValidationError
7
+ from django.db import models
8
+
9
+ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
10
+ from nautobot.core.models import BaseManager, BaseModel
11
+ from nautobot.core.models.fields import JSONArrayField
12
+ from nautobot.core.models.generics import PrimaryModel
13
+ from nautobot.core.models.querysets import RestrictedQuerySet
14
+ from nautobot.core.settings_funcs import is_truthy
15
+ from nautobot.extras.choices import MetadataTypeDataTypeChoices
16
+ from nautobot.extras.models.change_logging import ChangeLoggedModel
17
+ from nautobot.extras.models.contacts import Contact, Team
18
+ from nautobot.extras.utils import extras_features, FeatureQuery
19
+
20
+
21
+ class MetadataTypeManager(BaseManager.from_queryset(RestrictedQuerySet)):
22
+ use_in_migrations = True
23
+
24
+ def get_for_model(self, model):
25
+ """Return all MetadataTypes assigned to the given model."""
26
+ concrete_model = model._meta.concrete_model
27
+ cache_key = f"{self.get_for_model.cache_key_prefix}.{concrete_model._meta.label_lower}"
28
+ queryset = cache.get(cache_key)
29
+ if queryset is None:
30
+ content_type = ContentType.objects.get_for_model(concrete_model)
31
+ queryset = self.get_queryset().filter(content_types=content_type)
32
+ cache.set(cache_key, queryset)
33
+ return queryset
34
+
35
+ get_for_model.cache_key_prefix = "nautobot.extras.metadatatype.get_for_model"
36
+
37
+
38
+ @extras_features(
39
+ "custom_links",
40
+ "custom_validators",
41
+ "export_templates",
42
+ "graphql",
43
+ "webhooks",
44
+ )
45
+ class MetadataType(PrimaryModel):
46
+ content_types = models.ManyToManyField(
47
+ to=ContentType,
48
+ related_name="metadata_types",
49
+ limit_choices_to=FeatureQuery("metadata"),
50
+ help_text="The object type(s) to which Metadata of this type can be applied.",
51
+ )
52
+ name = models.CharField(max_length=CHARFIELD_MAX_LENGTH, unique=True)
53
+ description = models.CharField(max_length=CHARFIELD_MAX_LENGTH, blank=True)
54
+ data_type = models.CharField(
55
+ max_length=50,
56
+ choices=MetadataTypeDataTypeChoices,
57
+ help_text="The type of data allowed for any Metadata of this type.",
58
+ )
59
+ # TODO: validation_minimum, validation_maximum, validation_regex?
60
+ # TODO: weight, grouping, advanced_ui?
61
+
62
+ objects = MetadataTypeManager()
63
+ clone_fields = ["data_type"]
64
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
65
+
66
+ class Meta:
67
+ ordering = ["name"]
68
+
69
+ def __str__(self):
70
+ return self.name
71
+
72
+ def clean(self):
73
+ super().clean()
74
+
75
+ if self.present_in_database:
76
+ # Check immutable fields
77
+ database_object = self.__class__.objects.get(pk=self.pk)
78
+ if self.data_type != database_object.data_type:
79
+ raise ValidationError({"data_type": "Type cannot be changed once created"})
80
+
81
+ # Choices can be set only on selection fields
82
+ if self.choices.exists() and self.data_type not in (
83
+ MetadataTypeDataTypeChoices.TYPE_SELECT,
84
+ MetadataTypeDataTypeChoices.TYPE_MULTISELECT,
85
+ ):
86
+ raise ValidationError("Choices may be set only for select/multi-select data_type.")
87
+
88
+
89
+ @extras_features(
90
+ "custom_validators",
91
+ "graphql",
92
+ "webhooks",
93
+ )
94
+ class MetadataChoice(ChangeLoggedModel, BaseModel):
95
+ """Store the possible set of values for a select/multi-select metadata type."""
96
+
97
+ metadata_type = models.ForeignKey(
98
+ to=MetadataType,
99
+ on_delete=models.CASCADE,
100
+ related_name="choices",
101
+ limit_choices_to=models.Q(
102
+ data_type__in=[MetadataTypeDataTypeChoices.TYPE_SELECT, MetadataTypeDataTypeChoices.TYPE_MULTISELECT]
103
+ ),
104
+ )
105
+ value = models.CharField(max_length=CHARFIELD_MAX_LENGTH)
106
+ weight = models.PositiveSmallIntegerField(default=100, help_text="Higher weights appear later in the list")
107
+ is_metadata_associable_model = False
108
+
109
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
110
+
111
+ class Meta:
112
+ ordering = ["metadata_type", "weight", "value"]
113
+ unique_together = ["metadata_type", "value"]
114
+
115
+ def __str__(self):
116
+ return self.value
117
+
118
+ def clean(self):
119
+ super().clean()
120
+
121
+ if self.present_in_database:
122
+ # Check immutable fields
123
+ database_object = self.__class__.objects.get(pk=self.pk)
124
+ if self.metadata_type != database_object.metadata_type:
125
+ raise ValidationError({"metadata_type": "Cannot be changed once created"})
126
+
127
+ if self.metadata_type.data_type not in (
128
+ MetadataTypeDataTypeChoices.TYPE_SELECT,
129
+ MetadataTypeDataTypeChoices.TYPE_MULTISELECT,
130
+ ):
131
+ raise ValidationError(
132
+ {"metadata_type": "Metadata choices can only be assigned to select/multiselect data_type."}
133
+ )
134
+ # TODO: enforce validation_minimum, validation_maximum, validation_regex like CustomFieldChoice does?
135
+
136
+ def save(self, *args, **kwargs):
137
+ super().save(*args, **kwargs)
138
+ # TODO: on change of value, enqueue background task to go through all existing metadata and update them,
139
+ # like CustomFieldChoice does?
140
+ # TODO: Alternately, should we let Metadata just have a FK directly to its corresponding MetadataTypeChoice?
141
+
142
+ def delete(self, *args, **kwargs):
143
+ # TODO: should we behave like CustomFieldChoice and raise a ProtectedError if this choice is in use anywhere?
144
+ super().delete(*args, **kwargs)
145
+
146
+
147
+ class ObjectMetadataManager(BaseManager.from_queryset(RestrictedQuerySet)):
148
+ use_in_migrations = True
149
+
150
+ def get_for_object(self, instance):
151
+ """Return all ObjectMetadatas assigned to the given instance."""
152
+ assigned_object_type = ContentType.objects.get_for_model(instance)
153
+ assigned_object_id = instance.pk
154
+ queryset = self.get_queryset().filter(
155
+ assigned_object_type=assigned_object_type, assigned_object_id=assigned_object_id
156
+ )
157
+ return queryset
158
+
159
+
160
+ @extras_features(
161
+ "custom_validators",
162
+ "graphql",
163
+ "webhooks",
164
+ )
165
+ class ObjectMetadata(ChangeLoggedModel, BaseModel):
166
+ metadata_type = models.ForeignKey(
167
+ to=MetadataType,
168
+ on_delete=models.PROTECT,
169
+ related_name="object_metadata",
170
+ )
171
+ contact = models.ForeignKey(
172
+ to=Contact,
173
+ on_delete=models.PROTECT,
174
+ related_name="object_metadata",
175
+ blank=True,
176
+ null=True,
177
+ )
178
+ team = models.ForeignKey(
179
+ to=Team,
180
+ on_delete=models.PROTECT,
181
+ related_name="object_metadata",
182
+ blank=True,
183
+ null=True,
184
+ )
185
+ scoped_fields = JSONArrayField(
186
+ base_field=models.CharField(
187
+ max_length=CHARFIELD_MAX_LENGTH,
188
+ ),
189
+ blank=True,
190
+ help_text="List of scoped fields, only direct fields on the model",
191
+ )
192
+ _value = models.JSONField(
193
+ blank=True,
194
+ null=True,
195
+ help_text="Relevant data value to an object field or a set of object fields",
196
+ )
197
+ assigned_object_type = models.ForeignKey(to=ContentType, on_delete=models.SET_NULL, null=True, related_name="+")
198
+ assigned_object_id = models.UUIDField(db_index=True)
199
+ assigned_object = GenericForeignKey(ct_field="assigned_object_type", fk_field="assigned_object_id")
200
+
201
+ objects = ObjectMetadataManager()
202
+ natural_key_field_names = ["pk"]
203
+ documentation_static_path = "docs/user-guide/platform-functionality/objectmetadata.html"
204
+
205
+ class Meta:
206
+ ordering = ["metadata_type"]
207
+ verbose_name_plural = "object metadata"
208
+
209
+ indexes = [
210
+ models.Index(
211
+ name="assigned_object",
212
+ fields=["assigned_object_type", "assigned_object_id"],
213
+ ),
214
+ models.Index(
215
+ name="assigned_object_contact",
216
+ fields=["assigned_object_type", "assigned_object_id", "contact"],
217
+ ),
218
+ models.Index(
219
+ name="assigned_object_team",
220
+ fields=["assigned_object_type", "assigned_object_id", "team"],
221
+ ),
222
+ ]
223
+
224
+ @property
225
+ def value(self):
226
+ if self.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_CONTACT_TEAM:
227
+ return None
228
+ else:
229
+ return self._value
230
+
231
+ @value.setter
232
+ def value(self, v):
233
+ if self.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_CONTACT_TEAM:
234
+ if v is not None:
235
+ raise ValidationError(
236
+ f"{v} is an invalid value for metadata type data type {self.metadata_type.data_type}"
237
+ )
238
+ else:
239
+ self._value = v
240
+ self.clean()
241
+
242
+ def __str__(self):
243
+ if self.metadata_type.data_type == MetadataTypeDataTypeChoices.TYPE_CONTACT_TEAM:
244
+ return f"{self.metadata_type} - {self.assigned_object} - {self.contact or self.team}"
245
+ return f"{self.metadata_type} - {self.assigned_object} - {self.value}"
246
+
247
+ def validated_save(self, *args, **kwargs):
248
+ # call clean() first so that the data type-conversion is done first
249
+ # so that clean_fields() would not raise any errors
250
+ self.clean()
251
+ return super().validated_save(*args, **kwargs)
252
+
253
+ def clean(self):
254
+ """
255
+ Validate a value according to the field's type validation rules.
256
+
257
+ Returns the value, possibly cleaned up
258
+ """
259
+ super().clean()
260
+ value = self.value
261
+ metadata_type_data_type = self.metadata_type.data_type
262
+ if self.present_in_database:
263
+ # Check immutable fields
264
+ database_object = self.__class__.objects.get(pk=self.pk)
265
+ if self.metadata_type != database_object.metadata_type:
266
+ raise ValidationError({"metadata_type": "Cannot be changed once created"})
267
+ # Validate the assigned_object_type is allowed in metadata_type's content_types
268
+ if not self.metadata_type.content_types.filter(pk=self.assigned_object_type.id).exists():
269
+ raise ValidationError(
270
+ f"Assigned Object Type {self.assigned_object_type} is not allowed by Metadata Type {self.metadata_type}"
271
+ )
272
+ # Check for MetadataTypeDataTypeChoices.TYPE_CONTACT_TEAM first
273
+ if metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_CONTACT_TEAM:
274
+ if value is not None:
275
+ raise ValidationError(f"A value cannot be specified for type {metadata_type_data_type}")
276
+ if self.contact is None and self.team is None:
277
+ raise ValidationError("Either a contact or a team must be specified")
278
+ if self.contact is not None and self.team is not None:
279
+ raise ValidationError("A contact and a team cannot be both specified at once")
280
+ elif value is None:
281
+ raise ValidationError(
282
+ f"value is a required field that cannot be empty for metadata type {metadata_type_data_type}."
283
+ )
284
+ else:
285
+ # Validate text field
286
+ if metadata_type_data_type in (
287
+ MetadataTypeDataTypeChoices.TYPE_TEXT,
288
+ MetadataTypeDataTypeChoices.TYPE_URL,
289
+ MetadataTypeDataTypeChoices.TYPE_MARKDOWN,
290
+ ):
291
+ if not isinstance(value, str):
292
+ raise ValidationError("Value must be a string")
293
+ # TODO uncomment this when MetaDataType validation_minimum, validation_maximum and validation_regex fields are implemented
294
+ # if self.metadata_type.validation_minimum is not None and len(value) < self.metadata_type.validation_minimum:
295
+ # raise ValidationError(f"Value must be at least {self.metadata_type.validation_minimum} characters in length")
296
+ # if self.metadata_type.validation_maximum is not None and len(value) > self.metadata_type.validation_maximum:
297
+ # raise ValidationError(f"Value must not exceed {self.metadata_type.validation_maximum} characters in length")
298
+ # if self.metadata_type.validation_regex and not re.search(self.metadata_type.validation_regex, value):
299
+ # raise ValidationError(f"Value must match regex '{self.metadata_type.validation_regex}'")
300
+
301
+ # Validate JSON
302
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_JSON:
303
+ pass
304
+ # TODO uncomment this when MetaDataType validation_minimum, validation_maximum and validation_regex fields are implemented
305
+ # if (
306
+ # self.metadata_type.validation_regex
307
+ # or self.metadata_type.validation_minimum is not None
308
+ # or self.metadata_type.validation_maximum is not None
309
+ # ):
310
+ # json_value = json.dumps(value)
311
+ # if (
312
+ # self.metadata_type.validation_minimum is not None
313
+ # and len(json_value) < self.metadata_type.validation_minimum
314
+ # ):
315
+ # raise ValidationError(
316
+ # f"Value must be at least {self.metadata_type.validation_minimum} characters in length"
317
+ # )
318
+ # if (
319
+ # self.metadata_type.validation_maximum is not None
320
+ # and len(json_value) > self.metadata_type.validation_maximum
321
+ # ):
322
+ # raise ValidationError(
323
+ # f"Value must not exceed {self.metadata_type.validation_maximum} characters in length"
324
+ # )
325
+ # if self.metadata_type.validation_regex and not re.search(
326
+ # self.metadata_type.validation_regex, json_value
327
+ # ):
328
+ # raise ValidationError(f"Value must match regex '{self.metadata_type.validation_regex}'")
329
+
330
+ # Validate integer
331
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_INTEGER:
332
+ try:
333
+ value = int(value)
334
+ except ValueError:
335
+ raise ValidationError("Value must be an integer.")
336
+ # TODO uncomment this when MetaDataType validation_minimum, validation_maximum and validation_regex fields are implemented
337
+ # if self.metadata_type.validation_minimum is not None and value < self.metadata_type.validation_minimum:
338
+ # raise ValidationError(f"Value must be at least {self.metadata_type.validation_minimum}")
339
+ # if self.metadata_type.validation_maximum is not None and value > self.metadata_type.validation_maximum:
340
+ # raise ValidationError(f"Value must not exceed {self.metadata_type.validation_maximum}")
341
+ # Validate integer
342
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_FLOAT:
343
+ try:
344
+ value = float(value)
345
+ except ValueError:
346
+ raise ValidationError("Value must be a float.")
347
+ # Validate boolean
348
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_BOOLEAN:
349
+ try:
350
+ value = is_truthy(value)
351
+ except ValueError as exc:
352
+ raise ValidationError("Value must be true or false.") from exc
353
+
354
+ # Validate date
355
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_DATE:
356
+ if not isinstance(value, date):
357
+ try:
358
+ datetime.strptime(value, "%Y-%m-%d")
359
+ except TypeError:
360
+ raise ValidationError("Value must be a date or str object.")
361
+ except ValueError:
362
+ raise ValidationError("Date values must be in the format YYYY-MM-DD.")
363
+ # Validate datetime
364
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_DATETIME:
365
+ if isinstance(value, datetime):
366
+ # check if datetime object has tzinfo
367
+ if value.tzinfo is None:
368
+ value = value.replace(tzinfo=timezone.utc) # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
369
+ value = value.replace(microsecond=0).isoformat() # pylint: disable=unexpected-keyword-arg,no-value-for-parameter
370
+ else:
371
+ acceptable_datetime_formats = [
372
+ "YYYY-MM-DDTHH:MM:SS",
373
+ "YYYY-MM-DDTHH:MM:SS(+,-)zzzz",
374
+ "YYYY-MM-DDTHH:MM:SS(+,-)zz:zz",
375
+ ]
376
+ try:
377
+ datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
378
+ except ValueError:
379
+ try:
380
+ # No time zone info entered so assume UTC timezone
381
+ datetime.strptime(value, "%Y-%m-%dT%H:%M:%S")
382
+ value += "+0000"
383
+ except ValueError:
384
+ raise ValidationError(
385
+ f"Datetime values must be in the following formats {acceptable_datetime_formats}"
386
+ )
387
+ except TypeError:
388
+ raise ValidationError("Value must be a datetime or str object.")
389
+ # Validate selected choice
390
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_SELECT:
391
+ if value not in self.metadata_type.choices.values_list("value", flat=True):
392
+ raise ValidationError(
393
+ f"Invalid choice ({value}). Available choices are: {', '.join(self.metadata_type.choices.values_list('value', flat=True))}"
394
+ )
395
+
396
+ elif metadata_type_data_type == MetadataTypeDataTypeChoices.TYPE_MULTISELECT:
397
+ if isinstance(value, str):
398
+ value = value.split(",")
399
+ if not set(value).issubset(self.metadata_type.choices.values_list("value", flat=True)):
400
+ raise ValidationError(
401
+ f"Invalid choice(s) ({value}). Available choices are: {', '.join(self.metadata_type.choices.values_list('value', flat=True))}"
402
+ )
403
+ self._value = value
404
+
405
+ # Check if there is any intersections of scoped_fields of ObjectMetadata instances in the database.
406
+ object_metadata_scoped_fields = (
407
+ ObjectMetadata.objects.filter(
408
+ metadata_type=self.metadata_type,
409
+ assigned_object_type=self.assigned_object_type,
410
+ assigned_object_id=self.assigned_object_id,
411
+ )
412
+ .exclude(pk=self.pk)
413
+ .values_list("scoped_fields", flat=True)
414
+ )
415
+ duplicate_scoped_fields_list = set([])
416
+
417
+ # TODO Make sure that scoped fields are all direct fields on the assigned_object_type model
418
+
419
+ for scoped_fields in object_metadata_scoped_fields:
420
+ duplicate_scoped_fields_list |= set(self.scoped_fields).intersection(scoped_fields)
421
+ # if self.scoped_fields or scoped_fields are [] which means all fields are scoped
422
+ # That means any fields in self.scoped_fields or scoped_fields are considered overlapped
423
+ if not self.scoped_fields:
424
+ raise ValidationError(
425
+ {
426
+ "scoped_fields": f"This Object Metadata {self} scoping all the fields for {self.assigned_object} has overlapped with the scoped fields of other Object Metadata instances."
427
+ }
428
+ )
429
+ if not scoped_fields:
430
+ raise ValidationError(
431
+ {
432
+ "scoped_fields": f"There are other Object Metadata instances of metadata type {self.metadata_type} scoping all the fields for {self.assigned_object}"
433
+ }
434
+ )
435
+
436
+ if duplicate_scoped_fields_list:
437
+ raise ValidationError(
438
+ {
439
+ "scoped_fields": f"There are other Object Metadata instances of metadata type {self.metadata_type} scoping the same fields {duplicate_scoped_fields_list} for {self.assigned_object}"
440
+ }
441
+ )
@@ -2,95 +2,79 @@
2
2
  Class-modifying mixins that need to be standalone to avoid circular imports.
3
3
  """
4
4
 
5
+ from django.contrib.contenttypes.fields import GenericRelation
6
+ from django.db import models
5
7
  from django.urls import NoReverseMatch, reverse
6
8
 
9
+ from nautobot.core.utils.deprecation import method_deprecated_in_favor_of
7
10
  from nautobot.core.utils.lookup import get_route_for_model
8
11
 
9
12
 
10
- class DynamicGroupMixin:
11
- """
12
- Adds properties to a model to facilitate reversing DynamicGroup membership:
13
+ class ContactMixin(models.Model):
14
+ """Abstract mixin for enabling Contact/Team association to a given model class."""
13
15
 
14
- - `dynamic_groups` - A QuerySet of `DynamicGroup` objects this instance is a member of, performs the most database queries.
15
- - `dynamic_groups_cached` - A QuerySet of `DynamicGroup` objects this instance is a member of, uses cached member list if available. Ideal for most use cases.
16
- - `dynamic_groups_list` - A list of `DynamicGroup` objects this instance is a member of, performs one less database query than `dynamic_groups`.
17
- - `dynamic_groups_list_cached` - A list of `DynamicGroup` objects this instance is a member of, uses cached member list if available. Performs no database queries in optimal conditions.
16
+ class Meta:
17
+ abstract = True
18
18
 
19
- All properties are cached on the instance after the first call. To clear the instance cache without re-instantiating the object, call `delattr(instance, "_[the_property_name]")`.
20
- EX: `delattr(instance, "_dynamic_groups")`
21
- """
19
+ is_contact_associable_model = True
22
20
 
23
- @property
24
- def dynamic_groups(self):
25
- """
26
- Return a queryset of `DynamicGroup` objects this instance is a member of.
21
+ # Reverse relation so that deleting a ContactMixin automatically deletes any ContactAssociations related to it.
22
+ associated_contacts = GenericRelation(
23
+ "extras.ContactAssociation",
24
+ content_type_field="associated_object_type",
25
+ object_id_field="associated_object_id",
26
+ related_query_name="associated_contacts_%(app_label)s_%(class)s", # e.g. 'associated_contacts_dcim_device'
27
+ )
27
28
 
28
- This will NOT use the cached member lists of the dynamic groups and will always query the database for each DynamicGroup.
29
29
 
30
- Additionally, this performs a final database query to turn the internal list into a queryset.
31
- """
32
- from nautobot.extras.models.groups import DynamicGroup
30
+ class DynamicGroupMixin:
31
+ """
32
+ DEPRECATED - use DynamicGroupsModelMixin instead if you need to mark a model as supporting Dynamic Groups.
33
33
 
34
- if not hasattr(self, "_dynamic_groups"):
35
- queryset = DynamicGroup.objects.get_for_object(self)
36
- self._dynamic_groups = queryset
34
+ This is necessary because DynamicGroupMixin was incorrectly not implemented as a subclass of models.Model,
35
+ and so it cannot properly implement Model behaviors like the `static_group_association_set` ReverseRelation.
36
+ However, adding this inheritance to DynamicGroupMixin itself would negatively impact existing migrations.
37
+ So unfortunately our best option is to deprecate this class and gradually convert core and app models alike
38
+ to the new DynamicGroupsModelMixin in its place.
37
39
 
38
- return self._dynamic_groups
40
+ Adds `dynamic_groups` property to a model to facilitate reversing (cached) DynamicGroup membership.
39
41
 
40
- @property
41
- def dynamic_groups_cached(self):
42
- """
43
- Return a queryset of `DynamicGroup` objects this instance is a member of.
42
+ If up-to-the-minute accuracy is necessary for your use case, it's up to you to call the
43
+ `DynamicGroup.update_cached_members()` API on any relevant DynamicGroups before accessing this property.
44
44
 
45
- This will use the cached member lists of the dynamic groups if available.
45
+ Other related properties added by this mixin should be considered obsolete.
46
+ """
46
47
 
47
- In optimal conditions this will incur a single database query to convert internal list into a queryset which is reasonably performant.
48
+ is_dynamic_group_associable_model = True
48
49
 
49
- This is the ideal property to use for most use cases.
50
+ @property
51
+ def dynamic_groups(self):
52
+ """
53
+ Return a queryset of (cached) `DynamicGroup` objects this instance is a member of.
50
54
  """
51
55
  from nautobot.extras.models.groups import DynamicGroup
52
56
 
53
- if not hasattr(self, "_dynamic_groups_cached"):
54
- queryset = DynamicGroup.objects.get_for_object(self, use_cache=True)
55
- self._dynamic_groups_cached = queryset
57
+ return DynamicGroup.objects.get_for_object(self)
56
58
 
57
- return self._dynamic_groups_cached
59
+ @property
60
+ @method_deprecated_in_favor_of(dynamic_groups.fget)
61
+ def dynamic_groups_cached(self):
62
+ """Deprecated - use `self.dynamic_groups` instead."""
63
+ return self.dynamic_groups
58
64
 
59
65
  @property
66
+ @method_deprecated_in_favor_of(dynamic_groups.fget)
60
67
  def dynamic_groups_list(self):
61
- """
62
- Return a list of `DynamicGroup` objects this instance is a member of.
63
-
64
- This will NOT use the cached member lists of the dynamic groups and will always query the database for each DynamicGroup.
65
-
66
- This saves a final query to turn the list into a queryset.
67
- """
68
- from nautobot.extras.models.groups import DynamicGroup
69
-
70
- if not hasattr(self, "_dynamic_groups_list"):
71
- dg_list = DynamicGroup.objects.get_list_for_object(self)
72
- self._dynamic_groups_list = dg_list
73
-
74
- return self._dynamic_groups_list
68
+ """Deprecated - use `list(self.dynamic_groups)` instead."""
69
+ return list(self.dynamic_groups)
75
70
 
76
71
  @property
72
+ @method_deprecated_in_favor_of(dynamic_groups.fget)
77
73
  def dynamic_groups_list_cached(self):
78
- """
79
- Return a list of `DynamicGroup` objects this instance is a member of.
80
-
81
- This will use the cached member lists of the dynamic groups if available.
82
-
83
- In optimal conditions this will incur no database queries.
84
- """
85
-
86
- from nautobot.extras.models.groups import DynamicGroup
87
-
88
- if not hasattr(self, "_dynamic_groups_list_cached"):
89
- dg_list = DynamicGroup.objects.get_list_for_object(self, use_cache=True)
90
- self._dynamic_groups_list_cached = dg_list
91
-
92
- return self._dynamic_groups_list_cached
74
+ """Deprecated - use `list(self.dynamic_groups)` instead."""
75
+ return self.dynamic_groups_list
93
76
 
77
+ # TODO may be able to remove this entirely???
94
78
  def get_dynamic_groups_url(self):
95
79
  """Return the dynamic groups URL for a given instance."""
96
80
  route = get_route_for_model(self, "dynamicgroups")
@@ -109,6 +93,23 @@ class DynamicGroupMixin:
109
93
  return None
110
94
 
111
95
 
96
+ class DynamicGroupsModelMixin(DynamicGroupMixin, models.Model):
97
+ """
98
+ Add this to models to make them fully support Dynamic Groups.
99
+ """
100
+
101
+ class Meta:
102
+ abstract = True
103
+
104
+ # Reverse relation so that deleting a DynamicGroupMixin automatically deletes any related StaticGroupAssociations
105
+ static_group_association_set = GenericRelation( # not "static_group_associations" as that'd collide on DynamicGroup
106
+ "extras.StaticGroupAssociation",
107
+ content_type_field="associated_object_type",
108
+ object_id_field="associated_object_id",
109
+ related_query_name="static_group_association_set_%(app_label)s_%(class)s",
110
+ )
111
+
112
+
112
113
  class NotesMixin:
113
114
  """
114
115
  Adds a `notes` property that returns a queryset of `Notes` membership.
@@ -141,3 +142,12 @@ class NotesMixin:
141
142
  continue
142
143
 
143
144
  return None
145
+
146
+
147
+ class SavedViewMixin(models.Model):
148
+ """Abstract mixin for enabling Saved View functionality to a given model class."""
149
+
150
+ class Meta:
151
+ abstract = True
152
+
153
+ is_saved_view_model = True