nautobot 2.2.0b1__py3-none-any.whl → 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (429) hide show
  1. nautobot/__init__.py +31 -0
  2. nautobot/apps/api.py +1 -2
  3. nautobot/apps/utils.py +4 -0
  4. nautobot/apps/views.py +2 -0
  5. nautobot/circuits/api/urls.py +1 -2
  6. nautobot/circuits/api/views.py +0 -12
  7. nautobot/circuits/apps.py +1 -1
  8. nautobot/circuits/tests/test_filters.py +1 -1
  9. nautobot/core/api/routers.py +50 -3
  10. nautobot/core/api/utils.py +4 -0
  11. nautobot/core/api/views.py +21 -15
  12. nautobot/core/cli/__init__.py +18 -11
  13. nautobot/core/constants.py +85 -0
  14. nautobot/core/filters.py +7 -1
  15. nautobot/core/forms/widgets.py +1 -2
  16. nautobot/core/graphql/schema.py +1 -0
  17. nautobot/core/management/commands/generate_test_data.py +4 -4
  18. nautobot/core/models/__init__.py +1 -0
  19. nautobot/core/settings.py +24 -3
  20. nautobot/core/settings.yaml +20 -0
  21. nautobot/core/signals.py +1 -0
  22. nautobot/core/tables.py +2 -1
  23. nautobot/core/templates/admin/base.html +23 -94
  24. nautobot/core/templates/generic/object_retrieve.html +2 -2
  25. nautobot/core/templates/graphene/graphiql.html +18 -47
  26. nautobot/core/templates/inc/footer.html +5 -5
  27. nautobot/core/templates/inc/javascript.html +4 -4
  28. nautobot/core/templates/inc/media.html +2 -2
  29. nautobot/core/templates/inc/nav_menu.html +0 -7
  30. nautobot/core/templates/nautobot_config.py.j2 +14 -1
  31. nautobot/core/templates/rest_framework/api.html +12 -5
  32. nautobot/core/templatetags/helpers.py +2 -2
  33. nautobot/core/testing/__init__.py +1 -1
  34. nautobot/core/testing/filters.py +1 -1
  35. nautobot/core/testing/views.py +30 -0
  36. nautobot/core/tests/integration/test_view_authentication.py +68 -0
  37. nautobot/core/tests/test_api.py +13 -6
  38. nautobot/core/tests/test_csv.py +5 -4
  39. nautobot/core/tests/test_filters.py +2 -1
  40. nautobot/core/tests/test_graphql.py +4 -14
  41. nautobot/core/tests/test_navigations.py +3 -0
  42. nautobot/core/tests/test_views.py +45 -16
  43. nautobot/core/utils/data.py +1 -2
  44. nautobot/core/utils/lookup.py +126 -0
  45. nautobot/core/views/__init__.py +3 -7
  46. nautobot/core/views/generic.py +24 -10
  47. nautobot/core/views/mixins.py +11 -4
  48. nautobot/core/views/renderers.py +11 -6
  49. nautobot/core/wsgi.py +9 -2
  50. nautobot/dcim/api/serializers.py +4 -4
  51. nautobot/dcim/api/urls.py +2 -3
  52. nautobot/dcim/api/views.py +7 -18
  53. nautobot/dcim/apps.py +8 -4
  54. nautobot/dcim/elevations.py +5 -1
  55. nautobot/dcim/factory.py +7 -7
  56. nautobot/dcim/filters/__init__.py +16 -17
  57. nautobot/dcim/forms.py +69 -48
  58. nautobot/dcim/homepage.py +11 -3
  59. nautobot/dcim/management/commands/migrate_location_contacts.py +218 -0
  60. nautobot/dcim/migrations/0057_controller_models.py +11 -70
  61. nautobot/dcim/models/__init__.py +2 -2
  62. nautobot/dcim/models/devices.py +14 -16
  63. nautobot/dcim/models/racks.py +1 -3
  64. nautobot/dcim/navigation.py +23 -31
  65. nautobot/dcim/signals.py +6 -6
  66. nautobot/dcim/tables/__init__.py +2 -2
  67. nautobot/dcim/tables/devices.py +13 -16
  68. nautobot/dcim/tables/template_code.py +1 -1
  69. nautobot/dcim/templates/dcim/controller_create.html +70 -0
  70. nautobot/dcim/templates/dcim/controller_retrieve.html +35 -18
  71. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +88 -0
  72. nautobot/dcim/templates/dcim/device/lldp_neighbors.html +74 -42
  73. nautobot/dcim/templates/dcim/device.html +11 -3
  74. nautobot/dcim/templates/dcim/device_edit.html +1 -1
  75. nautobot/dcim/templates/dcim/devicefamily_retrieve.html +4 -0
  76. nautobot/dcim/templates/dcim/softwareimagefile_retrieve.html +1 -1
  77. nautobot/dcim/tests/test_api.py +47 -6
  78. nautobot/dcim/tests/test_filters.py +92 -81
  79. nautobot/dcim/tests/test_forms.py +49 -2
  80. nautobot/dcim/tests/test_graphql.py +11 -1
  81. nautobot/dcim/tests/test_models.py +15 -15
  82. nautobot/dcim/tests/test_signals.py +3 -1
  83. nautobot/dcim/tests/test_views.py +24 -12
  84. nautobot/dcim/urls.py +1 -1
  85. nautobot/dcim/views.py +25 -15
  86. nautobot/extras/api/serializers.py +20 -1
  87. nautobot/extras/api/urls.py +1 -2
  88. nautobot/extras/api/views.py +0 -10
  89. nautobot/extras/apps.py +7 -0
  90. nautobot/extras/context_managers.py +71 -4
  91. nautobot/extras/filters/__init__.py +53 -2
  92. nautobot/extras/filters/customfields.py +14 -9
  93. nautobot/extras/filters/mixins.py +6 -1
  94. nautobot/extras/forms/contacts.py +7 -0
  95. nautobot/extras/health_checks.py +1 -0
  96. nautobot/extras/jobs.py +1 -0
  97. nautobot/extras/managers.py +15 -2
  98. nautobot/extras/models/contacts.py +1 -0
  99. nautobot/extras/models/customfields.py +25 -2
  100. nautobot/extras/models/datasources.py +1 -0
  101. nautobot/extras/models/mixins.py +1 -0
  102. nautobot/extras/navigation.py +71 -65
  103. nautobot/extras/plugins/__init__.py +2 -1
  104. nautobot/extras/plugins/views.py +7 -11
  105. nautobot/extras/querysets.py +1 -2
  106. nautobot/extras/secrets/providers.py +1 -0
  107. nautobot/extras/signals.py +95 -51
  108. nautobot/extras/tasks.py +70 -17
  109. nautobot/extras/tests/test_api.py +2 -4
  110. nautobot/extras/tests/test_context_managers.py +98 -1
  111. nautobot/extras/tests/test_customfields.py +72 -9
  112. nautobot/extras/tests/test_dynamicgroups.py +2 -0
  113. nautobot/extras/tests/test_filters.py +89 -4
  114. nautobot/extras/tests/test_models.py +9 -0
  115. nautobot/extras/tests/test_relationships.py +10 -1
  116. nautobot/extras/tests/test_views.py +112 -1
  117. nautobot/extras/utils.py +37 -0
  118. nautobot/extras/views.py +18 -17
  119. nautobot/ipam/api/serializers.py +10 -0
  120. nautobot/ipam/api/urls.py +1 -2
  121. nautobot/ipam/api/views.py +0 -11
  122. nautobot/ipam/apps.py +3 -2
  123. nautobot/ipam/tables.py +3 -23
  124. nautobot/ipam/tests/test_graphql.py +2 -3
  125. nautobot/ipam/tests/test_tables.py +42 -0
  126. nautobot/ipam/tests/test_views.py +1 -0
  127. nautobot/ipam/views.py +9 -9
  128. nautobot/project-static/css/base.css +1 -0
  129. nautobot/project-static/docs/404.html +126 -73
  130. nautobot/project-static/docs/apps/index.html +127 -71
  131. nautobot/project-static/docs/apps/nautobot-apps.html +127 -71
  132. nautobot/project-static/docs/assets/javascripts/{bundle.8fd75fb4.min.js → bundle.bd41221c.min.js} +2 -2
  133. nautobot/project-static/docs/assets/javascripts/{bundle.8fd75fb4.min.js.map → bundle.bd41221c.min.js.map} +3 -3
  134. nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css +1 -0
  135. nautobot/project-static/docs/assets/stylesheets/main.bcfcd587.min.css.map +1 -0
  136. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +127 -71
  137. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +127 -71
  138. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +167 -73
  139. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +165 -72
  140. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +127 -71
  141. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +127 -71
  142. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +127 -71
  143. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +127 -71
  144. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +127 -71
  145. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +127 -71
  146. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +127 -71
  147. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +127 -71
  148. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +127 -71
  149. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +127 -71
  150. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +127 -71
  151. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +127 -71
  152. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +127 -71
  153. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +127 -71
  154. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +128 -72
  155. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +127 -71
  156. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +127 -71
  157. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +345 -71
  158. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +172 -73
  159. nautobot/project-static/docs/development/apps/api/configuration-view.html +127 -71
  160. nautobot/project-static/docs/development/apps/api/database-backend-config.html +127 -71
  161. nautobot/project-static/docs/development/apps/api/models/django-admin.html +127 -71
  162. nautobot/project-static/docs/development/apps/api/models/global-search.html +127 -71
  163. nautobot/project-static/docs/development/apps/api/models/graphql.html +127 -71
  164. nautobot/project-static/docs/development/apps/api/models/index.html +127 -71
  165. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +127 -71
  166. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +127 -71
  167. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +127 -71
  168. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +127 -71
  169. nautobot/project-static/docs/development/apps/api/platform-features/index.html +127 -71
  170. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +127 -71
  171. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +127 -71
  172. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +127 -71
  173. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +127 -71
  174. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +127 -71
  175. nautobot/project-static/docs/development/apps/api/prometheus.html +127 -71
  176. nautobot/project-static/docs/development/apps/api/setup.html +127 -71
  177. nautobot/project-static/docs/development/apps/api/testing.html +127 -71
  178. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +127 -71
  179. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +127 -71
  180. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +127 -71
  181. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +127 -71
  182. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +127 -71
  183. nautobot/project-static/docs/development/apps/api/views/base-template.html +127 -71
  184. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +141 -80
  185. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +144 -83
  186. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +127 -71
  187. nautobot/project-static/docs/development/apps/api/views/index.html +127 -71
  188. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +127 -71
  189. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +127 -71
  190. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +127 -71
  191. nautobot/project-static/docs/development/apps/api/views/notes.html +127 -71
  192. nautobot/project-static/docs/development/apps/api/views/rest-api.html +127 -71
  193. nautobot/project-static/docs/development/apps/api/views/urls.html +127 -71
  194. nautobot/project-static/docs/development/apps/index.html +127 -71
  195. nautobot/project-static/docs/development/apps/migration/code-updates.html +127 -71
  196. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +127 -71
  197. nautobot/project-static/docs/development/apps/migration/from-v1.html +127 -71
  198. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +127 -71
  199. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +127 -71
  200. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +127 -71
  201. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +127 -71
  202. nautobot/project-static/docs/development/apps/porting-from-netbox.html +127 -71
  203. nautobot/project-static/docs/development/core/application-registry.html +127 -71
  204. nautobot/project-static/docs/development/core/best-practices.html +145 -79
  205. nautobot/project-static/docs/development/core/bootstrap-ui.html +127 -71
  206. nautobot/project-static/docs/development/core/caching.html +127 -71
  207. nautobot/project-static/docs/development/core/controllers.html +141 -275
  208. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +127 -71
  209. nautobot/project-static/docs/development/core/extending-models.html +13 -8166
  210. nautobot/project-static/docs/development/core/generic-views.html +142 -86
  211. nautobot/project-static/docs/development/core/getting-started.html +146 -81
  212. nautobot/project-static/docs/development/core/homepage.html +145 -89
  213. nautobot/project-static/docs/development/core/index.html +127 -71
  214. nautobot/project-static/docs/development/core/model-checklist.html +8354 -0
  215. nautobot/project-static/docs/development/core/model-features.html +130 -74
  216. nautobot/project-static/docs/development/core/natural-keys.html +127 -71
  217. nautobot/project-static/docs/development/core/navigation-menu.html +127 -71
  218. nautobot/project-static/docs/development/core/release-checklist.html +127 -71
  219. nautobot/project-static/docs/development/core/role-internals.html +127 -71
  220. nautobot/project-static/docs/development/core/settings.html +127 -71
  221. nautobot/project-static/docs/development/core/style-guide.html +127 -71
  222. nautobot/project-static/docs/development/core/templates.html +127 -71
  223. nautobot/project-static/docs/development/core/testing.html +127 -71
  224. nautobot/project-static/docs/development/core/user-preferences.html +127 -71
  225. nautobot/project-static/docs/development/extending-models.html +3 -3
  226. nautobot/project-static/docs/development/index.html +127 -71
  227. nautobot/project-static/docs/development/jobs/index.html +128 -72
  228. nautobot/project-static/docs/development/jobs/migration/from-v1.html +127 -71
  229. nautobot/project-static/docs/index.html +126 -73
  230. nautobot/project-static/docs/models/dcim/{controllerdevicegroup.html → controllermanageddevicegroup.html} +3 -3
  231. nautobot/project-static/docs/objects.inv +0 -0
  232. nautobot/project-static/docs/release-notes/index.html +127 -71
  233. nautobot/project-static/docs/release-notes/version-1.0.html +127 -71
  234. nautobot/project-static/docs/release-notes/version-1.1.html +127 -71
  235. nautobot/project-static/docs/release-notes/version-1.2.html +127 -71
  236. nautobot/project-static/docs/release-notes/version-1.3.html +127 -71
  237. nautobot/project-static/docs/release-notes/version-1.4.html +127 -71
  238. nautobot/project-static/docs/release-notes/version-1.5.html +127 -71
  239. nautobot/project-static/docs/release-notes/version-1.6.html +663 -304
  240. nautobot/project-static/docs/release-notes/version-2.0.html +127 -71
  241. nautobot/project-static/docs/release-notes/version-2.1.html +538 -254
  242. nautobot/project-static/docs/release-notes/version-2.2.html +711 -125
  243. nautobot/project-static/docs/requirements.txt +3 -3
  244. nautobot/project-static/docs/search/search_index.json +1 -1
  245. nautobot/project-static/docs/sitemap.xml +264 -259
  246. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  247. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +127 -71
  248. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +127 -71
  249. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +127 -71
  250. nautobot/project-static/docs/user-guide/administration/configuration/index.html +127 -71
  251. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +192 -71
  252. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +127 -71
  253. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +127 -71
  254. nautobot/project-static/docs/user-guide/administration/guides/caching.html +127 -71
  255. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +127 -71
  256. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +127 -71
  257. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +127 -71
  258. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -71
  259. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +127 -71
  260. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +127 -71
  261. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +130 -74
  262. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +127 -71
  263. nautobot/project-static/docs/user-guide/administration/installation/docker.html +134 -74
  264. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +127 -71
  265. nautobot/project-static/docs/user-guide/administration/installation/health-checks.html +8616 -0
  266. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +127 -71
  267. nautobot/project-static/docs/user-guide/administration/installation/index.html +127 -71
  268. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +127 -71
  269. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +127 -71
  270. nautobot/project-static/docs/user-guide/administration/installation/selinux-troubleshooting.html +130 -74
  271. nautobot/project-static/docs/user-guide/administration/installation/services.html +127 -71
  272. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +127 -71
  273. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +127 -71
  274. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +127 -71
  275. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +127 -71
  276. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +127 -71
  277. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +127 -71
  278. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +127 -71
  279. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +127 -71
  280. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +127 -71
  281. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +127 -71
  282. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +127 -71
  283. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +127 -71
  284. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +127 -71
  285. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +127 -71
  286. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +127 -71
  287. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +127 -71
  288. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +127 -71
  289. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +127 -71
  290. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +127 -71
  291. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +127 -71
  292. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +127 -71
  293. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +127 -71
  294. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +127 -71
  295. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +362 -79
  296. nautobot/project-static/docs/user-guide/core-data-model/dcim/{controllerdevicegroup.html → controllermanageddevicegroup.html} +210 -85
  297. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +127 -71
  298. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +127 -71
  299. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +127 -71
  300. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +127 -71
  301. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +127 -71
  302. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +127 -71
  303. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +127 -71
  304. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +127 -71
  305. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +127 -71
  306. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +127 -71
  307. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +127 -71
  308. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +127 -71
  309. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +127 -71
  310. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +127 -71
  311. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +127 -71
  312. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +127 -71
  313. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +127 -71
  314. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +127 -71
  315. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +127 -71
  316. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +127 -71
  317. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +127 -71
  318. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +127 -71
  319. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +127 -71
  320. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +127 -71
  321. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +127 -71
  322. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +127 -71
  323. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +127 -71
  324. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +127 -71
  325. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +127 -71
  326. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +127 -71
  327. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +130 -74
  328. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +127 -71
  329. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +138 -71
  330. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +138 -71
  331. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +127 -71
  332. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +127 -71
  333. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +127 -71
  334. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +127 -71
  335. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +127 -71
  336. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +127 -71
  337. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +127 -71
  338. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +127 -71
  339. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +127 -71
  340. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +127 -71
  341. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +127 -71
  342. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +127 -71
  343. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +127 -71
  344. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +127 -71
  345. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +127 -71
  346. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +127 -71
  347. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +127 -71
  348. nautobot/project-static/docs/user-guide/feature-guides/{contact-and-team.html → contacts-and-teams.html} +128 -72
  349. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +129 -73
  350. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +127 -71
  351. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +127 -71
  352. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +127 -71
  353. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +127 -71
  354. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +127 -71
  355. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +127 -71
  356. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +129 -73
  357. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +127 -71
  358. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +127 -71
  359. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +127 -71
  360. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +127 -71
  361. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +127 -71
  362. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +127 -71
  363. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +127 -71
  364. nautobot/project-static/docs/user-guide/index.html +127 -71
  365. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +127 -71
  366. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +127 -71
  367. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +127 -71
  368. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +127 -71
  369. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +127 -71
  370. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +127 -71
  371. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +127 -71
  372. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +127 -71
  373. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +127 -71
  374. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +127 -71
  375. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +127 -71
  376. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +127 -71
  377. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +127 -71
  378. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +127 -71
  379. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +127 -71
  380. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +127 -71
  381. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +127 -71
  382. nautobot/project-static/docs/user-guide/platform-functionality/note.html +127 -71
  383. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +127 -71
  384. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +127 -71
  385. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +127 -71
  386. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +127 -71
  387. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +127 -71
  388. nautobot/project-static/docs/user-guide/platform-functionality/role.html +127 -71
  389. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +127 -71
  390. nautobot/project-static/docs/user-guide/platform-functionality/status.html +127 -71
  391. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +127 -71
  392. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +127 -71
  393. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +127 -71
  394. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +127 -71
  395. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +127 -71
  396. nautobot/project-static/jquery/jquery-3.7.1.min.js +2 -0
  397. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_444444_256x240.png +0 -0
  398. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_555555_256x240.png +0 -0
  399. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_777620_256x240.png +0 -0
  400. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_777777_256x240.png +0 -0
  401. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_cc0000_256x240.png +0 -0
  402. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/images/ui-icons_ffffff_256x240.png +0 -0
  403. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.css +7 -0
  404. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.min.js +6 -0
  405. nautobot/project-static/jquery-ui-1.13.2/jquery-ui.structure.min.css +5 -0
  406. nautobot/project-static/{jquery-ui-1.13.1 → jquery-ui-1.13.2}/jquery-ui.theme.min.css +1 -1
  407. nautobot/tenancy/api/urls.py +1 -2
  408. nautobot/tenancy/api/views.py +0 -12
  409. nautobot/tenancy/tables.py +1 -1
  410. nautobot/tenancy/tests/test_views.py +1 -0
  411. nautobot/users/api/urls.py +1 -2
  412. nautobot/users/api/views.py +2 -65
  413. nautobot/users/views.py +8 -8
  414. nautobot/virtualization/api/urls.py +1 -2
  415. nautobot/virtualization/api/views.py +0 -12
  416. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/METADATA +24 -24
  417. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/RECORD +422 -416
  418. nautobot/dcim/templates/dcim/controllerdevicegroup_create.html +0 -43
  419. nautobot/project-static/docs/assets/stylesheets/main.f2e4d321.min.css +0 -1
  420. nautobot/project-static/docs/assets/stylesheets/main.f2e4d321.min.css.map +0 -1
  421. nautobot/project-static/jquery/jquery-3.6.0.min.js +0 -2
  422. nautobot/project-static/jquery-ui-1.13.1/jquery-ui.min.css +0 -7
  423. nautobot/project-static/jquery-ui-1.13.1/jquery-ui.min.js +0 -6
  424. nautobot/project-static/jquery-ui-1.13.1/jquery-ui.structure.min.css +0 -5
  425. /nautobot/dcim/templates/dcim/{controllerdevicegroup_retrieve.html → controllermanageddevicegroup_retrieve.html} +0 -0
  426. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/LICENSE.txt +0 -0
  427. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/NOTICE +0 -0
  428. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/WHEEL +0 -0
  429. {nautobot-2.2.0b1.dist-info → nautobot-2.2.2.dist-info}/entry_points.txt +0 -0
@@ -724,7 +724,16 @@ class CustomField(BaseModel, ChangeLoggedModel, NotesMixin):
724
724
 
725
725
  super().delete(*args, **kwargs)
726
726
 
727
- delete_custom_field_data.delay(self.key, content_types)
727
+ # Circular Import
728
+ from nautobot.extras.signals import change_context_state
729
+
730
+ change_context = change_context_state.get()
731
+ if change_context is None:
732
+ context = None
733
+ else:
734
+ context = change_context.as_dict(instance=self)
735
+ context["context_detail"] = "delete custom field data"
736
+ delete_custom_field_data.delay(self.key, content_types, context)
728
737
 
729
738
  def add_prefix_to_cf_key(self):
730
739
  return "cf_" + str(self.key)
@@ -783,8 +792,22 @@ class CustomFieldChoice(BaseModel, ChangeLoggedModel):
783
792
  super().save(*args, **kwargs)
784
793
 
785
794
  if self.value != database_object.value:
795
+ # Circular Import
796
+ from nautobot.extras.signals import change_context_state
797
+
798
+ change_context = change_context_state.get()
799
+ if change_context is None:
800
+ context = None
801
+ else:
802
+ context = change_context.as_dict(instance=self)
803
+ context["context_detail"] = "update custom field choice data"
786
804
  transaction.on_commit(
787
- lambda: update_custom_field_choice_data.delay(self.custom_field.pk, database_object.value, self.value)
805
+ lambda: update_custom_field_choice_data.delay(
806
+ self.custom_field.pk,
807
+ database_object.value,
808
+ self.value,
809
+ context,
810
+ )
788
811
  )
789
812
 
790
813
  def delete(self, *args, **kwargs):
@@ -1,4 +1,5 @@
1
1
  """Models for representing external data sources."""
2
+
2
3
  from importlib.util import find_spec
3
4
  import os
4
5
 
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Class-modifying mixins that need to be standalone to avoid circular imports.
3
3
  """
4
+
4
5
  from django.urls import NoReverseMatch, reverse
5
6
 
6
7
  from nautobot.core.utils.lookup import get_route_for_model
@@ -14,8 +14,50 @@ menu_items = (
14
14
  weight=100,
15
15
  groups=(
16
16
  NavMenuGroup(
17
- name="Metadata", # TODO: is there a better name for this grouping?
17
+ name="Contacts",
18
18
  weight=400,
19
+ items=(
20
+ NavMenuItem(
21
+ link="extras:contact_list",
22
+ name="Contacts",
23
+ weight=100,
24
+ permissions=["extras.view_contact"],
25
+ buttons=[NavMenuAddButton(link="extras:contact_add", permissions=["extras.add_contact"])],
26
+ ),
27
+ NavMenuItem(
28
+ link="extras:team_list",
29
+ name="Teams",
30
+ weight=200,
31
+ permissions=["extras.view_team"],
32
+ buttons=[NavMenuAddButton(link="extras:team_add", permissions=["extras.add_team"])],
33
+ ),
34
+ ),
35
+ ),
36
+ NavMenuGroup(
37
+ name="Groups",
38
+ weight=500,
39
+ items=(
40
+ NavMenuItem(
41
+ link="extras:dynamicgroup_list",
42
+ name="Dynamic Groups",
43
+ weight=100,
44
+ permissions=[
45
+ "extras.view_dynamicgroup",
46
+ ],
47
+ buttons=(
48
+ NavMenuAddButton(
49
+ link="extras:dynamicgroup_add",
50
+ permissions=[
51
+ "extras.add_dynamicgroup",
52
+ ],
53
+ ),
54
+ ),
55
+ ),
56
+ ),
57
+ ),
58
+ NavMenuGroup(
59
+ name="Metadata", # TODO: is there a better name for this grouping?
60
+ weight=600,
19
61
  items=(
20
62
  NavMenuItem(
21
63
  link="extras:tag_list",
@@ -65,42 +107,6 @@ menu_items = (
65
107
  ),
66
108
  ),
67
109
  ),
68
- NavMenuItem(
69
- link="extras:contact_list",
70
- name="Contacts",
71
- weight=400,
72
- permissions=["extras.view_contact"],
73
- buttons=[NavMenuAddButton(link="extras:contact_add", permissions=["extras.add_contact"])],
74
- ),
75
- NavMenuItem(
76
- link="extras:team_list",
77
- name="Teams",
78
- weight=500,
79
- permissions=["extras.view_team"],
80
- buttons=[NavMenuAddButton(link="extras:team_add", permissions=["extras.add_team"])],
81
- ),
82
- ),
83
- ),
84
- NavMenuGroup(
85
- name="Dynamic Groups",
86
- weight=500,
87
- items=(
88
- NavMenuItem(
89
- link="extras:dynamicgroup_list",
90
- name="Dynamic Groups",
91
- weight=100,
92
- permissions=[
93
- "extras.view_dynamicgroup",
94
- ],
95
- buttons=(
96
- NavMenuAddButton(
97
- link="extras:dynamicgroup_add",
98
- permissions=[
99
- "extras.add_dynamicgroup",
100
- ],
101
- ),
102
- ),
103
- ),
104
110
  ),
105
111
  ),
106
112
  ),
@@ -276,22 +282,6 @@ menu_items = (
276
282
  ),
277
283
  ),
278
284
  ),
279
- NavMenuItem(
280
- link="extras:relationship_list",
281
- name="Relationships",
282
- weight=200,
283
- permissions=[
284
- "extras.view_relationship",
285
- ],
286
- buttons=(
287
- NavMenuAddButton(
288
- link="extras:relationship_add",
289
- permissions=[
290
- "extras.add_relationship",
291
- ],
292
- ),
293
- ),
294
- ),
295
285
  NavMenuItem(
296
286
  link="extras:note_list",
297
287
  name="Notes",
@@ -389,37 +379,53 @@ menu_items = (
389
379
  ),
390
380
  ),
391
381
  NavMenuGroup(
392
- name="Miscellaneous",
382
+ name="Data Model",
393
383
  weight=600,
394
384
  items=(
395
385
  NavMenuItem(
396
- link="extras:computedfield_list",
397
- name="Computed Fields",
386
+ link="extras:customfield_list",
387
+ name="Custom Fields",
398
388
  weight=100,
399
389
  permissions=[
400
- "extras.view_computedfield",
390
+ "extras.view_customfield",
401
391
  ],
402
392
  buttons=(
403
393
  NavMenuAddButton(
404
- link="extras:computedfield_add",
394
+ link="extras:customfield_add",
405
395
  permissions=[
406
- "extras.add_computedfield",
396
+ "extras.add_customfield",
407
397
  ],
408
398
  ),
409
399
  ),
410
400
  ),
411
401
  NavMenuItem(
412
- link="extras:customfield_list",
413
- name="Custom Fields",
402
+ link="extras:relationship_list",
403
+ name="Relationships",
414
404
  weight=200,
415
405
  permissions=[
416
- "extras.view_customfield",
406
+ "extras.view_relationship",
417
407
  ],
418
408
  buttons=(
419
409
  NavMenuAddButton(
420
- link="extras:customfield_add",
410
+ link="extras:relationship_add",
421
411
  permissions=[
422
- "extras.add_customfield",
412
+ "extras.add_relationship",
413
+ ],
414
+ ),
415
+ ),
416
+ ),
417
+ NavMenuItem(
418
+ link="extras:computedfield_list",
419
+ name="Computed Fields",
420
+ weight=300,
421
+ permissions=[
422
+ "extras.view_computedfield",
423
+ ],
424
+ buttons=(
425
+ NavMenuAddButton(
426
+ link="extras:computedfield_add",
427
+ permissions=[
428
+ "extras.add_computedfield",
423
429
  ],
424
430
  ),
425
431
  ),
@@ -427,7 +433,7 @@ menu_items = (
427
433
  NavMenuItem(
428
434
  link="extras:customlink_list",
429
435
  name="Custom Links",
430
- weight=300,
436
+ weight=400,
431
437
  permissions=[
432
438
  "extras.view_customlink",
433
439
  ],
@@ -4,6 +4,7 @@ from importlib import import_module
4
4
  import inspect
5
5
  from logging import getLogger
6
6
 
7
+ from django.conf import settings
7
8
  from django.core.exceptions import ValidationError
8
9
  from django.template.loader import get_template
9
10
  from django.urls import get_resolver, URLPattern
@@ -147,7 +148,7 @@ class NautobotAppConfig(NautobotConfig):
147
148
 
148
149
  # Import metrics (if present)
149
150
  metrics = import_object(f"{self.__module__}.{self.metrics}")
150
- if metrics is not None:
151
+ if metrics is not None and self.name not in settings.METRICS_DISABLED_APPS:
151
152
  register_metrics(metrics)
152
153
  self.features["metrics"] = [] # Initialize as empty, to be filled by the signal handler
153
154
  # Inject the metrics to discover into the signal handler.
@@ -2,11 +2,9 @@ from collections import OrderedDict
2
2
 
3
3
  from django.apps import apps
4
4
  from django.conf import settings
5
- from django.contrib.auth.mixins import LoginRequiredMixin
6
5
  from django.http import Http404
7
6
  from django.shortcuts import render
8
7
  from django.urls.exceptions import NoReverseMatch
9
- from django.views.generic import View
10
8
  from django_tables2 import RequestConfig
11
9
  from drf_spectacular.utils import extend_schema
12
10
  from rest_framework import permissions
@@ -14,13 +12,14 @@ from rest_framework.response import Response
14
12
  from rest_framework.reverse import reverse
15
13
  from rest_framework.views import APIView
16
14
 
17
- from nautobot.core.api.views import NautobotAPIVersionMixin
15
+ from nautobot.core.api.views import AuthenticatedAPIRootView, NautobotAPIVersionMixin
18
16
  from nautobot.core.forms import TableConfigForm
17
+ from nautobot.core.views.generic import GenericView
19
18
  from nautobot.core.views.paginator import EnhancedPaginator, get_paginate_count
20
19
  from nautobot.extras.plugins.tables import InstalledAppsTable
21
20
 
22
21
 
23
- class InstalledAppsView(LoginRequiredMixin, View):
22
+ class InstalledAppsView(GenericView):
24
23
  """
25
24
  View for listing all installed Apps.
26
25
  """
@@ -66,7 +65,7 @@ class InstalledAppsView(LoginRequiredMixin, View):
66
65
  )
67
66
 
68
67
 
69
- class InstalledAppDetailView(LoginRequiredMixin, View):
68
+ class InstalledAppDetailView(GenericView):
70
69
  """
71
70
  View for showing details of an installed App.
72
71
  """
@@ -93,7 +92,6 @@ class InstalledAppsAPIView(NautobotAPIVersionMixin, APIView):
93
92
  """
94
93
 
95
94
  permission_classes = [permissions.IsAdminUser]
96
- _ignore_model_permissions = True
97
95
 
98
96
  def get_view_name(self):
99
97
  return "Installed Apps"
@@ -129,11 +127,9 @@ class InstalledAppsAPIView(NautobotAPIVersionMixin, APIView):
129
127
  return Response([self._get_app_data(apps.get_app_config(app)) for app in settings.PLUGINS])
130
128
 
131
129
 
132
- class AppsAPIRootView(NautobotAPIVersionMixin, APIView):
133
- _ignore_model_permissions = True
134
-
135
- def get_view_name(self):
136
- return "Apps"
130
+ class AppsAPIRootView(AuthenticatedAPIRootView):
131
+ name = "Apps"
132
+ description = "API extension point for installed Nautobot Apps"
137
133
 
138
134
  @staticmethod
139
135
  def _get_app_entry(app_config, request, format_):
@@ -3,7 +3,6 @@ from django.contrib.contenttypes.models import ContentType
3
3
  from django.core.cache import cache
4
4
  from django.db.models import F, Model, OuterRef, Q, Subquery
5
5
  from django.db.models.functions import JSONObject
6
- from django_celery_beat.managers import ExtendedQuerySet
7
6
 
8
7
  from nautobot.core.models.query_functions import EmptyGroupByJSONBAgg
9
8
  from nautobot.core.models.querysets import RestrictedQuerySet
@@ -272,7 +271,7 @@ class JobQuerySet(RestrictedQuerySet):
272
271
  )
273
272
 
274
273
 
275
- class ScheduledJobExtendedQuerySet(RestrictedQuerySet, ExtendedQuerySet):
274
+ class ScheduledJobExtendedQuerySet(RestrictedQuerySet):
276
275
  """
277
276
  Base queryset used for the ScheduledJob class
278
277
  """
@@ -2,6 +2,7 @@
2
2
 
3
3
  Plugins may define and register additional providers in addition to these.
4
4
  """
5
+
5
6
  import os
6
7
 
7
8
  from django import forms
@@ -52,7 +52,7 @@ logger = logging.getLogger(__name__)
52
52
  #
53
53
 
54
54
 
55
- def _get_user_if_authenticated(user, instance):
55
+ def get_user_if_authenticated(user, instance):
56
56
  """Return the user object associated with the request if the user is defined.
57
57
 
58
58
  If the user is not defined, log a warning to indicate that the user couldn't be retrived from the request
@@ -103,7 +103,9 @@ def _handle_changed_object(sender, instance, raw=False, **kwargs):
103
103
  if raw:
104
104
  return
105
105
 
106
- if change_context_state.get() is None:
106
+ change_context = change_context_state.get()
107
+
108
+ if change_context is None:
107
109
  return
108
110
 
109
111
  # Determine the type of change being made
@@ -119,36 +121,49 @@ def _handle_changed_object(sender, instance, raw=False, **kwargs):
119
121
 
120
122
  # Record an ObjectChange if applicable
121
123
  if hasattr(instance, "to_objectchange"):
122
- user = _get_user_if_authenticated(change_context_state.get().get_user(), instance)
124
+ user = change_context.get_user(instance)
123
125
  # save a copy of this instance's field cache so it can be restored after serialization
124
126
  # to prevent unexpected behavior when chaining multiple signal handlers
125
127
  original_cache = instance._state.fields_cache.copy()
126
128
 
129
+ changed_object_type = ContentType.objects.get_for_model(instance)
130
+ changed_object_id = instance.id
131
+
132
+ # Generate a unique identifier for this change to stash in the change context
133
+ # This is used for deferred change logging and for looking up related changes without querying the database
134
+ unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
135
+
127
136
  # If a change already exists for this change_id, user, and object, update it instead of creating a new one.
128
137
  # If the object was deleted then recreated with the same pk (don't do this), change the action to update.
129
- related_changes = ObjectChange.objects.filter(
130
- changed_object_type=ContentType.objects.get_for_model(instance),
131
- changed_object_id=instance.pk,
132
- user=user,
133
- request_id=change_context_state.get().change_id,
134
- )
135
- objectchange = instance.to_objectchange(action)
136
- if related_changes.exists():
137
- most_recent_change = related_changes.order_by("-time").first()
138
- if most_recent_change.action == ObjectChangeActionChoices.ACTION_DELETE:
139
- most_recent_change.action = ObjectChangeActionChoices.ACTION_UPDATE
140
- most_recent_change.object_data = objectchange.object_data
141
- most_recent_change.object_data_v2 = objectchange.object_data_v2
142
- most_recent_change.save()
143
- objectchange = most_recent_change
138
+ if unique_object_change_id in change_context.deferred_object_changes:
139
+ related_changes = ObjectChange.objects.filter(
140
+ changed_object_type=changed_object_type,
141
+ changed_object_id=changed_object_id,
142
+ user=user,
143
+ request_id=change_context.change_id,
144
+ )
145
+
146
+ # Skip the database check when deferring object changes
147
+ if not change_context.defer_object_changes and related_changes.exists():
148
+ objectchange = instance.to_objectchange(action)
149
+ most_recent_change = related_changes.order_by("-time").first()
150
+ if most_recent_change.action == ObjectChangeActionChoices.ACTION_DELETE:
151
+ most_recent_change.action = ObjectChangeActionChoices.ACTION_UPDATE
152
+ most_recent_change.object_data = objectchange.object_data
153
+ most_recent_change.object_data_v2 = objectchange.object_data_v2
154
+ most_recent_change.save()
155
+
144
156
  else:
145
- objectchange.user = user
146
- objectchange.request_id = change_context_state.get().change_id
147
- objectchange.change_context = change_context_state.get().context
148
- objectchange.change_context_detail = change_context_state.get().context_detail[
149
- :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
157
+ change_context.deferred_object_changes[unique_object_change_id] = [
158
+ {"action": action, "instance": instance, "user": user}
150
159
  ]
151
- objectchange.save()
160
+ if not change_context.defer_object_changes:
161
+ objectchange = instance.to_objectchange(action)
162
+ objectchange.user = user
163
+ objectchange.request_id = change_context.change_id
164
+ objectchange.change_context = change_context.context
165
+ objectchange.change_context_detail = change_context.context_detail[:CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL]
166
+ objectchange.save()
152
167
 
153
168
  # restore field cache
154
169
  instance._state.fields_cache = original_cache
@@ -171,7 +186,9 @@ def _handle_deleted_object(sender, instance, **kwargs):
171
186
  """
172
187
  Fires when an object is deleted.
173
188
  """
174
- if change_context_state.get() is None:
189
+ change_context = change_context_state.get()
190
+
191
+ if change_context is None:
175
192
  return
176
193
 
177
194
  if isinstance(instance, BaseModel):
@@ -186,41 +203,58 @@ def _handle_deleted_object(sender, instance, **kwargs):
186
203
 
187
204
  # Record an ObjectChange if applicable
188
205
  if hasattr(instance, "to_objectchange"):
189
- user = _get_user_if_authenticated(change_context_state.get().get_user(), instance)
206
+ user = change_context.get_user(instance)
190
207
 
191
208
  # save a copy of this instance's field cache so it can be restored after serialization
192
209
  # to prevent unexpected behavior when chaining multiple signal handlers
193
210
  original_cache = instance._state.fields_cache.copy()
194
211
 
212
+ changed_object_type = ContentType.objects.get_for_model(instance)
213
+ changed_object_id = instance.id
214
+
215
+ # Generate a unique identifier for this change to stash in the change context
216
+ # This is used for deferred change logging and for looking up related changes without querying the database
217
+ unique_object_change_id = f"{changed_object_type.pk}__{changed_object_id}__{user.pk}"
218
+ save_new_objectchange = True
219
+
195
220
  # if a change already exists for this change_id, user, and object, update it instead of creating a new one
196
221
  # except in the case that the object was created and deleted in the same change_id
197
222
  # we don't want to create a delete change for an object that never existed
198
- related_changes = ObjectChange.objects.filter(
199
- changed_object_type=ContentType.objects.get_for_model(instance),
200
- changed_object_id=instance.pk,
201
- user=user,
202
- request_id=change_context_state.get().change_id,
203
- )
204
- objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
205
- save_new_objectchange = True
206
- if related_changes.exists():
207
- most_recent_change = related_changes.order_by("-time").first()
208
- if most_recent_change.action != ObjectChangeActionChoices.ACTION_CREATE:
209
- most_recent_change.action = ObjectChangeActionChoices.ACTION_DELETE
210
- most_recent_change.object_data = objectchange.object_data
211
- most_recent_change.object_data_v2 = objectchange.object_data_v2
212
- most_recent_change.save()
213
- objectchange = most_recent_change
223
+ if unique_object_change_id in change_context.deferred_object_changes:
224
+ cached_related_change = change_context.deferred_object_changes[unique_object_change_id][-1]
225
+ if cached_related_change["action"] != ObjectChangeActionChoices.ACTION_CREATE:
226
+ cached_related_change["action"] = ObjectChangeActionChoices.ACTION_DELETE
214
227
  save_new_objectchange = False
215
228
 
229
+ related_changes = ObjectChange.objects.filter(
230
+ changed_object_type=changed_object_type,
231
+ changed_object_id=changed_object_id,
232
+ user=user,
233
+ request_id=change_context.change_id,
234
+ )
235
+
236
+ # Skip the database check when deferring object changes
237
+ if not change_context.defer_object_changes and related_changes.exists():
238
+ objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
239
+ most_recent_change = related_changes.order_by("-time").first()
240
+ if most_recent_change.action != ObjectChangeActionChoices.ACTION_CREATE:
241
+ most_recent_change.action = ObjectChangeActionChoices.ACTION_DELETE
242
+ most_recent_change.object_data = objectchange.object_data
243
+ most_recent_change.object_data_v2 = objectchange.object_data_v2
244
+ most_recent_change.save()
245
+ save_new_objectchange = False
246
+
216
247
  if save_new_objectchange:
217
- objectchange.user = user
218
- objectchange.request_id = change_context_state.get().change_id
219
- objectchange.change_context = change_context_state.get().context
220
- objectchange.change_context_detail = change_context_state.get().context_detail[
221
- :CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL
222
- ]
223
- objectchange.save()
248
+ change_context.deferred_object_changes.setdefault(unique_object_change_id, []).append(
249
+ {"action": ObjectChangeActionChoices.ACTION_DELETE, "instance": instance, "user": user}
250
+ )
251
+ if not change_context.defer_object_changes:
252
+ objectchange = instance.to_objectchange(ObjectChangeActionChoices.ACTION_DELETE)
253
+ objectchange.user = user
254
+ objectchange.request_id = change_context.change_id
255
+ objectchange.change_context = change_context.context
256
+ objectchange.change_context_detail = change_context.context_detail[:CHANGELOG_MAX_CHANGE_CONTEXT_DETAIL]
257
+ objectchange.save()
224
258
 
225
259
  # restore field cache
226
260
  instance._state.fields_cache = original_cache
@@ -238,13 +272,23 @@ def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
238
272
  """
239
273
  Handle the cleanup of old custom field data when a CustomField is removed from one or more ContentTypes.
240
274
  """
275
+
276
+ change_context = change_context_state.get()
277
+ if change_context is None:
278
+ context = None
279
+ else:
280
+ context = change_context.as_dict(instance=instance)
241
281
  if action == "post_remove":
242
282
  # Existing content types have been removed from the custom field, delete their data
243
- transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, pk_set))
283
+ if context:
284
+ context["context_detail"] = "delete custom field data from existing content types"
285
+ transaction.on_commit(lambda: delete_custom_field_data.delay(instance.key, pk_set, context))
244
286
 
245
287
  elif action == "post_add":
246
288
  # New content types have been added to the custom field, provision them
247
- transaction.on_commit(lambda: provision_field.delay(instance.pk, pk_set))
289
+ if context:
290
+ context["context_detail"] = "provision custom field data for new content types"
291
+ transaction.on_commit(lambda: provision_field.delay(instance.pk, pk_set, context))
248
292
 
249
293
 
250
294
  m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.content_types.through)