nautobot 2.2.5__py3-none-any.whl → 2.2.7__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 (345) hide show
  1. nautobot/apps/api.py +2 -0
  2. nautobot/apps/models.py +2 -0
  3. nautobot/core/api/fields.py +13 -0
  4. nautobot/core/api/mixins.py +6 -1
  5. nautobot/core/api/schema.py +3 -1
  6. nautobot/core/api/serializers.py +7 -1
  7. nautobot/core/celery/__init__.py +1 -1
  8. nautobot/core/management/commands/generate_test_data.py +128 -158
  9. nautobot/core/models/fields.py +15 -0
  10. nautobot/core/tests/runner.py +10 -0
  11. nautobot/core/tests/test_utils.py +48 -1
  12. nautobot/core/utils/git.py +121 -49
  13. nautobot/core/utils/module_loading.py +10 -2
  14. nautobot/dcim/factory.py +1 -1
  15. nautobot/dcim/tests/test_models.py +2 -0
  16. nautobot/extras/datasources/git.py +133 -135
  17. nautobot/extras/datasources/utils.py +3 -0
  18. nautobot/extras/filters/__init__.py +1 -0
  19. nautobot/extras/forms/forms.py +16 -3
  20. nautobot/extras/jobs.py +9 -1
  21. nautobot/extras/migrations/0107_laxurlfield.py +28 -0
  22. nautobot/extras/migrations/0108_jobbutton_enabled.py +17 -0
  23. nautobot/extras/models/datasources.py +6 -4
  24. nautobot/extras/models/groups.py +9 -2
  25. nautobot/extras/models/jobs.py +30 -0
  26. nautobot/extras/models/models.py +2 -4
  27. nautobot/extras/tables.py +3 -0
  28. nautobot/extras/templates/extras/jobbutton_retrieve.html +6 -2
  29. nautobot/extras/templatetags/job_buttons.py +2 -2
  30. nautobot/extras/tests/git_data/01-valid-files/__init__.py +0 -0
  31. nautobot/extras/tests/git_data/01-valid-files/config_context_schemas/schema-1.yaml +18 -0
  32. nautobot/extras/tests/git_data/01-valid-files/config_contexts/context.yaml +12 -0
  33. nautobot/extras/tests/git_data/01-valid-files/config_contexts/devices/test-device.json +3 -0
  34. nautobot/extras/tests/git_data/01-valid-files/config_contexts/locations/Test Location.json +7 -0
  35. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template.j2 +3 -0
  36. nautobot/extras/tests/git_data/01-valid-files/export_templates/dcim/device/template2.html +4 -0
  37. nautobot/extras/tests/git_data/01-valid-files/export_templates/ipam/vlan/template.j2 +3 -0
  38. nautobot/extras/tests/git_data/01-valid-files/jobs/__init__.py +5 -0
  39. nautobot/extras/tests/git_data/01-valid-files/jobs/my_job.py +16 -0
  40. nautobot/extras/tests/git_data/02-invalid-files/__init__.py +0 -0
  41. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema1.json +2 -0
  42. nautobot/extras/tests/git_data/02-invalid-files/config_context_schemas/badschema2.json +1 -0
  43. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext1.json +2 -0
  44. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext2.json +1 -0
  45. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/badcontext3.json +3 -0
  46. nautobot/extras/tests/git_data/02-invalid-files/config_contexts/devices/nosuchdevice.json +1 -0
  47. nautobot/extras/tests/git_data/02-invalid-files/dcim/template.j2 +0 -0
  48. nautobot/extras/tests/git_data/02-invalid-files/devices/template.j2 +0 -0
  49. nautobot/extras/tests/git_data/02-invalid-files/export_templates/dcim/nosuchmodel/template.j2 +3 -0
  50. nautobot/extras/tests/git_data/02-invalid-files/export_templates/nosuchapp/device/template.j2 +3 -0
  51. nautobot/extras/tests/git_data/02-invalid-files/jobs/__init__.py +2 -0
  52. nautobot/extras/tests/git_data/02-invalid-files/jobs/importerror.py +1 -0
  53. nautobot/extras/tests/git_data/02-invalid-files/jobs/syntaxerror.py +1 -0
  54. nautobot/extras/tests/git_helper.py +76 -0
  55. nautobot/extras/tests/test_api.py +28 -11
  56. nautobot/extras/tests/test_datasources.py +94 -276
  57. nautobot/extras/tests/test_dynamicgroups.py +8 -1
  58. nautobot/extras/tests/test_models.py +8 -3
  59. nautobot/extras/tests/test_views.py +21 -7
  60. nautobot/extras/views.py +1 -1
  61. nautobot/ipam/api/serializers.py +46 -16
  62. nautobot/ipam/api/views.py +29 -16
  63. nautobot/ipam/filters.py +9 -1
  64. nautobot/ipam/forms.py +8 -0
  65. nautobot/ipam/tables.py +1 -1
  66. nautobot/ipam/tests/test_api.py +15 -20
  67. nautobot/ipam/tests/test_filters.py +15 -0
  68. nautobot/project-static/docs/404.html +83 -8
  69. nautobot/project-static/docs/apps/index.html +96 -10
  70. nautobot/project-static/docs/apps/nautobot-apps.html +96 -10
  71. nautobot/project-static/docs/assets/app-icons/icon-CapacityMetrics.svg +1 -0
  72. nautobot/project-static/docs/assets/app-icons/icon-CircuitMaintenance.png +0 -0
  73. nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js → bundle.fe8b6f2b.min.js} +4 -4
  74. nautobot/project-static/docs/assets/javascripts/{bundle.ebd0bdb7.min.js.map → bundle.fe8b6f2b.min.js.map} +3 -3
  75. nautobot/project-static/docs/assets/javascripts/glightbox.min.js +1 -0
  76. nautobot/project-static/docs/assets/stylesheets/glightbox.min.css +1 -0
  77. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +96 -10
  78. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +96 -10
  79. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +96 -10
  80. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +96 -10
  81. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +96 -10
  82. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +96 -10
  83. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +96 -10
  84. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +96 -10
  85. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +96 -10
  86. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +96 -10
  87. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +96 -10
  88. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +96 -10
  89. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +96 -10
  90. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +96 -10
  91. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +156 -12
  92. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +96 -10
  93. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +96 -10
  94. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +96 -10
  95. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +106 -11
  96. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +96 -10
  97. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +96 -10
  98. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +143 -11
  99. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +96 -10
  100. nautobot/project-static/docs/development/apps/api/configuration-view.html +96 -10
  101. nautobot/project-static/docs/development/apps/api/database-backend-config.html +96 -10
  102. nautobot/project-static/docs/development/apps/api/models/django-admin.html +96 -10
  103. nautobot/project-static/docs/development/apps/api/models/global-search.html +96 -10
  104. nautobot/project-static/docs/development/apps/api/models/graphql.html +96 -10
  105. nautobot/project-static/docs/development/apps/api/models/index.html +96 -10
  106. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +96 -10
  107. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +96 -10
  108. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +96 -10
  109. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +96 -10
  110. nautobot/project-static/docs/development/apps/api/platform-features/index.html +96 -10
  111. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +96 -10
  112. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +96 -10
  113. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +96 -10
  114. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +96 -10
  115. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +96 -10
  116. nautobot/project-static/docs/development/apps/api/prometheus.html +96 -10
  117. nautobot/project-static/docs/development/apps/api/setup.html +96 -10
  118. nautobot/project-static/docs/development/apps/api/testing.html +96 -10
  119. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +96 -10
  120. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +96 -10
  121. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +96 -10
  122. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +96 -10
  123. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +96 -10
  124. nautobot/project-static/docs/development/apps/api/views/base-template.html +96 -10
  125. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +96 -10
  126. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +96 -10
  127. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +96 -10
  128. nautobot/project-static/docs/development/apps/api/views/index.html +96 -10
  129. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +96 -10
  130. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +96 -10
  131. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +96 -10
  132. nautobot/project-static/docs/development/apps/api/views/notes.html +96 -10
  133. nautobot/project-static/docs/development/apps/api/views/rest-api.html +96 -10
  134. nautobot/project-static/docs/development/apps/api/views/urls.html +96 -10
  135. nautobot/project-static/docs/development/apps/index.html +96 -10
  136. nautobot/project-static/docs/development/apps/migration/code-updates.html +97 -11
  137. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +96 -10
  138. nautobot/project-static/docs/development/apps/migration/from-v1.html +96 -10
  139. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +96 -10
  140. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +96 -10
  141. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +96 -10
  142. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +96 -10
  143. nautobot/project-static/docs/development/apps/porting-from-netbox.html +96 -10
  144. nautobot/project-static/docs/development/core/application-registry.html +96 -10
  145. nautobot/project-static/docs/development/core/best-practices.html +96 -10
  146. nautobot/project-static/docs/development/core/bootstrap-ui.html +96 -10
  147. nautobot/project-static/docs/development/core/caching.html +96 -10
  148. nautobot/project-static/docs/development/core/controllers.html +96 -10
  149. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +96 -10
  150. nautobot/project-static/docs/development/core/generic-views.html +96 -10
  151. nautobot/project-static/docs/development/core/getting-started.html +98 -13
  152. nautobot/project-static/docs/development/core/homepage.html +96 -10
  153. nautobot/project-static/docs/development/core/index.html +96 -10
  154. nautobot/project-static/docs/development/core/model-checklist.html +96 -10
  155. nautobot/project-static/docs/development/core/model-features.html +96 -10
  156. nautobot/project-static/docs/development/core/natural-keys.html +96 -10
  157. nautobot/project-static/docs/development/core/navigation-menu.html +96 -10
  158. nautobot/project-static/docs/development/core/release-checklist.html +96 -10
  159. nautobot/project-static/docs/development/core/role-internals.html +96 -10
  160. nautobot/project-static/docs/development/core/settings.html +96 -10
  161. nautobot/project-static/docs/development/core/style-guide.html +96 -10
  162. nautobot/project-static/docs/development/core/templates.html +96 -10
  163. nautobot/project-static/docs/development/core/testing.html +109 -11
  164. nautobot/project-static/docs/development/core/user-preferences.html +96 -10
  165. nautobot/project-static/docs/development/index.html +96 -10
  166. nautobot/project-static/docs/development/jobs/index.html +96 -10
  167. nautobot/project-static/docs/development/jobs/migration/from-v1.html +96 -10
  168. nautobot/project-static/docs/index.html +13 -8362
  169. nautobot/project-static/docs/objects.inv +0 -0
  170. nautobot/project-static/docs/overview/application_stack.html +8229 -0
  171. nautobot/project-static/docs/overview/design_philosophy.html +8158 -0
  172. nautobot/project-static/docs/overview/index.html +8230 -0
  173. nautobot/project-static/docs/release-notes/index.html +96 -10
  174. nautobot/project-static/docs/release-notes/version-1.0.html +96 -10
  175. nautobot/project-static/docs/release-notes/version-1.1.html +96 -10
  176. nautobot/project-static/docs/release-notes/version-1.2.html +96 -10
  177. nautobot/project-static/docs/release-notes/version-1.3.html +96 -10
  178. nautobot/project-static/docs/release-notes/version-1.4.html +96 -10
  179. nautobot/project-static/docs/release-notes/version-1.5.html +96 -10
  180. nautobot/project-static/docs/release-notes/version-1.6.html +96 -10
  181. nautobot/project-static/docs/release-notes/version-2.0.html +96 -10
  182. nautobot/project-static/docs/release-notes/version-2.1.html +96 -10
  183. nautobot/project-static/docs/release-notes/version-2.2.html +516 -131
  184. nautobot/project-static/docs/requirements.txt +2 -1
  185. nautobot/project-static/docs/search/search_index.json +1 -1
  186. nautobot/project-static/docs/sitemap.xml +268 -258
  187. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  188. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +96 -10
  189. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +96 -10
  190. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +96 -10
  191. nautobot/project-static/docs/user-guide/administration/configuration/index.html +96 -10
  192. nautobot/project-static/docs/user-guide/administration/configuration/optional-settings.html +96 -10
  193. nautobot/project-static/docs/user-guide/administration/configuration/required-settings.html +96 -10
  194. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +96 -10
  195. nautobot/project-static/docs/user-guide/administration/guides/caching.html +96 -10
  196. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +96 -10
  197. nautobot/project-static/docs/user-guide/administration/guides/healthcheck.html +96 -10
  198. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +96 -10
  199. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +96 -10
  200. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +96 -10
  201. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +96 -10
  202. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +96 -10
  203. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +96 -10
  204. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +96 -10
  205. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +96 -10
  206. nautobot/project-static/docs/user-guide/administration/installation/index.html +96 -10
  207. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +96 -10
  208. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +96 -10
  209. nautobot/project-static/docs/user-guide/administration/installation/services.html +96 -10
  210. nautobot/project-static/docs/user-guide/administration/installation-extras/docker.html +96 -10
  211. nautobot/project-static/docs/user-guide/administration/installation-extras/health-checks.html +96 -10
  212. nautobot/project-static/docs/user-guide/administration/installation-extras/selinux-troubleshooting.html +96 -10
  213. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +96 -10
  214. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +96 -10
  215. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +96 -10
  216. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +96 -10
  217. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +96 -10
  218. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +96 -10
  219. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +96 -10
  220. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +96 -10
  221. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +96 -10
  222. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +96 -10
  223. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +96 -10
  224. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
  225. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +97 -11
  226. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +96 -10
  227. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +96 -10
  228. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +96 -10
  229. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +96 -10
  230. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +96 -10
  231. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +96 -10
  232. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +96 -10
  233. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +96 -10
  234. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +96 -10
  235. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +96 -10
  236. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +96 -10
  237. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +96 -10
  238. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +96 -10
  239. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +96 -10
  240. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +96 -10
  241. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +96 -10
  242. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +96 -10
  243. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +96 -10
  244. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +96 -10
  245. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +96 -10
  246. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +96 -10
  247. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +96 -10
  248. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +96 -10
  249. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +96 -10
  250. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +96 -10
  251. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +96 -10
  252. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +96 -10
  253. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +96 -10
  254. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +96 -10
  255. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +96 -10
  256. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +96 -10
  257. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +96 -10
  258. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +96 -10
  259. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +96 -10
  260. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +96 -10
  261. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +96 -10
  262. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +96 -10
  263. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +96 -10
  264. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +96 -10
  265. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +96 -10
  266. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +96 -10
  267. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +96 -10
  268. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +96 -10
  269. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +96 -10
  270. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +96 -10
  271. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +96 -10
  272. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +96 -10
  273. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +96 -10
  274. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +96 -10
  275. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +96 -10
  276. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +96 -10
  277. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +96 -10
  278. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +96 -10
  279. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +96 -10
  280. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +96 -10
  281. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +96 -10
  282. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +96 -10
  283. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +96 -10
  284. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +96 -10
  285. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +96 -10
  286. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +96 -10
  287. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +96 -10
  288. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +96 -10
  289. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +96 -10
  290. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +96 -10
  291. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +96 -10
  292. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +96 -10
  293. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +96 -10
  294. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +96 -10
  295. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +96 -10
  296. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +96 -10
  297. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +96 -10
  298. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +96 -10
  299. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +96 -10
  300. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +96 -10
  301. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +96 -10
  302. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +96 -10
  303. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +96 -10
  304. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +96 -10
  305. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +96 -10
  306. nautobot/project-static/docs/user-guide/index.html +99 -13
  307. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +96 -10
  308. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +96 -10
  309. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +96 -10
  310. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +96 -10
  311. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +96 -10
  312. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +96 -10
  313. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +96 -10
  314. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +96 -10
  315. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +96 -10
  316. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +96 -10
  317. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +96 -10
  318. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +97 -11
  319. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +96 -10
  320. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +101 -14
  321. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +96 -10
  322. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +96 -10
  323. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +96 -10
  324. nautobot/project-static/docs/user-guide/platform-functionality/note.html +96 -10
  325. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +96 -10
  326. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +96 -10
  327. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +96 -10
  328. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +96 -10
  329. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +96 -10
  330. nautobot/project-static/docs/user-guide/platform-functionality/role.html +96 -10
  331. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +96 -10
  332. nautobot/project-static/docs/user-guide/platform-functionality/status.html +96 -10
  333. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +96 -10
  334. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +96 -10
  335. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +96 -10
  336. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +96 -10
  337. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +96 -10
  338. nautobot/project-static/js/connection_toggles.js +7 -6
  339. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/METADATA +2 -2
  340. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/RECORD +344 -311
  341. nautobot/extras/tests/test_git.py +0 -23
  342. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/LICENSE.txt +0 -0
  343. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/NOTICE +0 -0
  344. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/WHEEL +0 -0
  345. {nautobot-2.2.5.dist-info → nautobot-2.2.7.dist-info}/entry_points.txt +0 -0
nautobot/apps/api.py CHANGED
@@ -12,6 +12,7 @@ from nautobot.core.api import (
12
12
  from nautobot.core.api.fields import (
13
13
  ChoiceField,
14
14
  ContentTypeField,
15
+ LaxURLField,
15
16
  NautobotHyperlinkedRelatedField,
16
17
  ObjectTypeField,
17
18
  SerializedPKRelatedField,
@@ -63,6 +64,7 @@ __all__ = (
63
64
  "get_view_name",
64
65
  "GetObjectCountsView",
65
66
  "is_api_request",
67
+ "LaxURLField",
66
68
  "ModelViewSet",
67
69
  "ModelViewSetMixin",
68
70
  "MultipleChoiceJSONField",
nautobot/apps/models.py CHANGED
@@ -8,6 +8,7 @@ from nautobot.core.models.fields import (
8
8
  ForeignKeyLimitedByContentTypes,
9
9
  ForeignKeyWithAutoRelatedName,
10
10
  JSONArrayField,
11
+ LaxURLField,
11
12
  mac_unix_expanded_uppercase,
12
13
  MACAddressCharField,
13
14
  NaturalOrderingField,
@@ -83,6 +84,7 @@ __all__ = (
83
84
  "is_taggable",
84
85
  "JSONArrayField",
85
86
  "JSONBAgg",
87
+ "LaxURLField",
86
88
  "mac_unix_expanded_uppercase",
87
89
  "MACAddressCharField",
88
90
  "NameColorContentTypesModel",
@@ -2,15 +2,18 @@ from collections import OrderedDict
2
2
  import logging
3
3
 
4
4
  from django.core.exceptions import ObjectDoesNotExist
5
+ from django.core.validators import URLValidator
5
6
  from django.db.models import Model
6
7
  from drf_spectacular.utils import extend_schema_field
7
8
  from rest_framework import serializers
8
9
  from rest_framework.exceptions import ValidationError
10
+ from rest_framework.fields import URLField
9
11
  from rest_framework.relations import PrimaryKeyRelatedField, RelatedField
10
12
  from timezone_field.rest_framework import TimeZoneSerializerField as TimeZoneSerializerField_
11
13
 
12
14
  from nautobot.core.api.mixins import WritableSerializerMixin
13
15
  from nautobot.core.models.utils import deconstruct_composite_key
16
+ from nautobot.core.models.validators import EnhancedURLValidator
14
17
  from nautobot.core.utils.data import is_url, is_uuid
15
18
  from nautobot.core.utils.lookup import get_route_for_model
16
19
 
@@ -124,6 +127,16 @@ class ContentTypeField(RelatedField):
124
127
  return f"{obj.app_label}.{obj.model}"
125
128
 
126
129
 
130
+ class LaxURLField(URLField):
131
+ def __init__(self, validators=None, **kwargs):
132
+ super().__init__(**kwargs)
133
+ # Discard default URLValidator added by URLField
134
+ self.validators = [v for v in self.validators if not isinstance(v, URLValidator)]
135
+ if validators is not None:
136
+ self.validators.extend(validators)
137
+ self.validators.append(EnhancedURLValidator(message=self.error_messages["invalid"]))
138
+
139
+
127
140
  @extend_schema_field(
128
141
  {
129
142
  "type": "object",
@@ -6,7 +6,7 @@ from django.core.exceptions import (
6
6
  MultipleObjectsReturned,
7
7
  ObjectDoesNotExist,
8
8
  )
9
- from django.db.models import AutoField
9
+ from django.db.models import AutoField, Model
10
10
  from rest_framework.exceptions import ValidationError
11
11
 
12
12
  from nautobot.core.api.utils import dict_to_filter_params
@@ -70,6 +70,11 @@ class WritableSerializerMixin:
70
70
  # Strip the trailing slash and split on slashes, taking the last value as the PK.
71
71
  data = data.rstrip("/").split("/")[-1]
72
72
 
73
+ # If we're passing the validated_data from one serializer as input to another serializer,
74
+ # data might already be a model instance:
75
+ if isinstance(data, Model):
76
+ return {"pk": data.pk}
77
+
73
78
  try:
74
79
  # The int case here is taking into account for the User model we inherit from django
75
80
  pk = int(data) if isinstance(queryset.model._meta.pk, AutoField) else uuid.UUID(str(data))
@@ -4,7 +4,7 @@ import re
4
4
  from drf_spectacular.contrib.django_filters import DjangoFilterExtension
5
5
  from drf_spectacular.extensions import OpenApiSerializerFieldExtension
6
6
  from drf_spectacular.openapi import AutoSchema
7
- from drf_spectacular.plumbing import build_array_type, build_media_type_object, get_doc, is_serializer
7
+ from drf_spectacular.plumbing import build_array_type, build_media_type_object, get_doc, is_serializer, list_hash
8
8
  from drf_spectacular.serializers import PolymorphicProxySerializerExtension
9
9
  from rest_framework import serializers
10
10
  from rest_framework.relations import ManyRelatedField
@@ -359,6 +359,8 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
359
359
  return {
360
360
  "type": value_type,
361
361
  "enum": list(choices.keys()),
362
+ # Used to deduplicate enums with the same set of choices, since drf-spectacular 0.27.2
363
+ "x-spec-enum-id": list_hash([(k, v) for k, v in choices.items() if k not in ("", None)]),
362
364
  }
363
365
  else:
364
366
  return {
@@ -26,12 +26,13 @@ from rest_framework.serializers import SerializerMethodField
26
26
  from rest_framework.utils.model_meta import _get_to_field, RelationInfo
27
27
 
28
28
  from nautobot.core import constants
29
- from nautobot.core.api.fields import NautobotHyperlinkedRelatedField, ObjectTypeField
29
+ from nautobot.core.api.fields import LaxURLField, NautobotHyperlinkedRelatedField, ObjectTypeField
30
30
  from nautobot.core.api.utils import (
31
31
  dict_to_filter_params,
32
32
  nested_serializer_factory,
33
33
  )
34
34
  from nautobot.core.exceptions import ViewConfigException
35
+ from nautobot.core.models.fields import LaxURLField as LaxURLModelField
35
36
  from nautobot.core.models.managers import TagsManager
36
37
  from nautobot.core.models.utils import construct_composite_key, construct_natural_slug
37
38
  from nautobot.core.templatetags.helpers import bettertitle
@@ -126,6 +127,11 @@ class BaseModelSerializer(OptInFieldsMixin, serializers.HyperlinkedModelSerializ
126
127
  to enable the dynamic generation of nested serializers.
127
128
  """
128
129
 
130
+ serializer_field_mapping = {
131
+ **serializers.ModelSerializer.serializer_field_mapping,
132
+ LaxURLModelField: LaxURLField,
133
+ }
134
+
129
135
  serializer_related_field = NautobotHyperlinkedRelatedField
130
136
 
131
137
  display = serializers.SerializerMethodField(read_only=True, help_text="Human friendly display value")
@@ -109,7 +109,7 @@ def _import_jobs_from_git_repositories():
109
109
  and not GitRepository.objects.filter(slug=filename).exists()
110
110
  ):
111
111
  logger.warning("Deleting unmanaged (leftover?) Git repository clone at %s", filepath)
112
- shutil.rmtree(filepath)
112
+ shutil.rmtree(filepath, ignore_errors=True)
113
113
 
114
114
  # Make sure all GitRepository records that include Jobs have up-to-date git clones, and load their jobs
115
115
  for repo in GitRepository.objects.filter(provided_contents__contains="extras.job"):
@@ -30,6 +30,17 @@ class Command(BaseCommand):
30
30
  dest="interactive",
31
31
  help="Do NOT prompt the user for input or confirmation of any kind.",
32
32
  )
33
+ parser.add_argument(
34
+ "--print-hashes",
35
+ action="store_true",
36
+ help=(
37
+ "After creating each batch of records, print a hash of the list of all IDs of all objects of "
38
+ "the given type. This is useful for identifying any problems with factory randomness / determinism; "
39
+ "in general, successive runs with the same seed should output identical hashes for each stage, "
40
+ "while successive runs with differing seeds should output different hashes. "
41
+ "Setting environment variable GITHUB_ACTIONS to true is equivalent to specifying this argument."
42
+ ),
43
+ )
33
44
  parser.add_argument(
34
45
  "--cache-test-fixtures",
35
46
  action="store_true",
@@ -46,7 +57,7 @@ class Command(BaseCommand):
46
57
  help='The database to generate the test data in. Defaults to the "default" database.',
47
58
  )
48
59
 
49
- def _generate_factory_data(self, seed, db_name):
60
+ def _generate_factory_data(self, seed, db_name, print_hashes=False):
50
61
  try:
51
62
  import factory.random
52
63
 
@@ -83,7 +94,6 @@ class Command(BaseCommand):
83
94
  from nautobot.extras.utils import TaggableClassesQuery
84
95
  from nautobot.ipam.choices import PrefixTypeChoices
85
96
  from nautobot.ipam.factory import (
86
- IPAddressFactory,
87
97
  NamespaceFactory,
88
98
  PrefixFactory,
89
99
  RIRFactory,
@@ -101,178 +111,135 @@ class Command(BaseCommand):
101
111
  self.stdout.write(f'Seeding the pseudo-random number generator with seed "{seed}"...')
102
112
  factory.random.reseed_random(seed)
103
113
 
104
- self.stdout.write("Creating Roles...")
114
+ def _create_batch(some_factory, count, description="", **kwargs):
115
+ model = some_factory._meta.get_model_class()
116
+ if description:
117
+ description = " " + description
118
+ message = f"Creating {count} {model._meta.verbose_name_plural}{description}..."
119
+ self.stdout.write(message)
120
+ records = some_factory.create_batch(count, using=db_name, **kwargs)
121
+ if print_hashes:
122
+ model_ids = [record.id for record in records]
123
+ sha256_hash = hashlib.sha256(json.dumps(model_ids, cls=DjangoJSONEncoder).encode()).hexdigest()
124
+ self.stdout.write(f" SHA256: {sha256_hash}")
125
+
105
126
  populate_role_choices(verbosity=0, using=db_name)
106
- RoleFactory.create_batch(20)
107
- self.stdout.write("Creating Statuses...")
127
+ _create_batch(RoleFactory, 20)
108
128
  populate_status_choices(verbosity=0, using=db_name)
109
- StatusFactory.create_batch(10, using=db_name)
110
- self.stdout.write("Creating Tags...")
129
+ _create_batch(StatusFactory, 10)
111
130
  # Ensure that we have some tags that are applicable to all relevant content-types
112
- TagFactory.create_batch(5, content_types=TaggableClassesQuery().as_queryset(), using=db_name)
131
+ _create_batch(
132
+ TagFactory, 5, description="on all content-types", content_types=TaggableClassesQuery().as_queryset()
133
+ )
113
134
  # ...and some tags that apply to a random subset of content-types
114
- TagFactory.create_batch(15, using=db_name)
115
- self.stdout.write("Creating Contacts...")
116
- ContactFactory.create_batch(20, using=db_name)
117
- self.stdout.write("Creating Teams...")
118
- TeamFactory.create_batch(20, using=db_name)
119
- self.stdout.write("Creating TenantGroups...")
120
- TenantGroupFactory.create_batch(10, has_parent=False, using=db_name)
121
- TenantGroupFactory.create_batch(10, has_parent=True, using=db_name)
122
- self.stdout.write("Creating Tenants...")
123
- TenantFactory.create_batch(10, has_tenant_group=False, using=db_name)
124
- TenantFactory.create_batch(10, has_tenant_group=True, using=db_name)
125
- self.stdout.write("Creating LocationTypes...")
126
- LocationTypeFactory.create_batch(7, using=db_name) # only 7 unique LocationTypes are hard-coded presently
127
- self.stdout.write("Creating Locations...")
135
+ _create_batch(TagFactory, 15, description="on some content-types")
136
+ _create_batch(ContactFactory, 20)
137
+ _create_batch(TeamFactory, 20)
138
+ _create_batch(TenantGroupFactory, 10, description="without parents", has_parent=False)
139
+ _create_batch(TenantGroupFactory, 10, description="with parents", has_parent=True)
140
+ _create_batch(TenantFactory, 10, description="without a parent group", has_tenant_group=False)
141
+ _create_batch(TenantFactory, 10, description="with a parent group", has_tenant_group=True)
142
+ _create_batch(LocationTypeFactory, 7) # only 7 unique LocationTypes are hard-coded presently
128
143
  # First 7 locations must be created in specific order so subsequent objects have valid parents to reference
129
- LocationFactory.create_batch(7, has_parent=True, using=db_name)
130
- LocationFactory.create_batch(40, using=db_name)
131
- LocationFactory.create_batch(10, has_parent=False, using=db_name)
132
- self.stdout.write("Creating Controller with Groups...")
133
- ControllerFactory.create_batch(1)
134
- ControllerManagedDeviceGroupFactory.create_batch(5)
135
- self.stdout.write("Creating RIRs...")
136
- RIRFactory.create_batch(9, using=db_name) # only 9 unique RIR names are hard-coded presently
137
- self.stdout.write("Creating RouteTargets...")
138
- RouteTargetFactory.create_batch(20, using=db_name)
139
- self.stdout.write("Creating Namespaces...")
140
- NamespaceFactory.create_batch(10, using=db_name)
141
- self.stdout.write("Creating VRFs...")
142
- VRFFactory.create_batch(10, has_tenant=True, using=db_name)
143
- VRFFactory.create_batch(10, has_tenant=False, using=db_name)
144
- self.stdout.write("Creating VLANGroups...")
145
- VLANGroupFactory.create_batch(20, using=db_name)
146
- self.stdout.write("Creating VLANs...")
147
- VLANFactory.create_batch(20, using=db_name)
148
- self.stdout.write("Creating Prefixes and IP Addresses...")
144
+ _create_batch(LocationFactory, 7, description="as structure", has_parent=True)
145
+ _create_batch(LocationFactory, 40)
146
+ _create_batch(LocationFactory, 10, description="without a parent Location", has_parent=False)
147
+ _create_batch(ControllerFactory, 1, description="without a Device or DeviceRedundancyGroup")
148
+ _create_batch(ControllerManagedDeviceGroupFactory, 5, description="to contain Devices")
149
+ _create_batch(RIRFactory, 9) # only 9 unique RIR names are hard-coded presently
150
+ _create_batch(RouteTargetFactory, 20)
151
+ _create_batch(NamespaceFactory, 10)
152
+ _create_batch(VRFFactory, 20)
153
+ _create_batch(VLANGroupFactory, 20)
154
+ _create_batch(VLANFactory, 20)
149
155
  for i in range(30):
150
- PrefixFactory.create(prefix=f"10.{i}.0.0/16", type=PrefixTypeChoices.TYPE_CONTAINER, using=db_name)
151
- PrefixFactory.create(prefix=f"2001:db8:0:{i}::/64", type=PrefixTypeChoices.TYPE_CONTAINER, using=db_name)
152
- self.stdout.write("Creating Empty Namespaces...")
153
- NamespaceFactory.create_batch(5, using=db_name)
154
- self.stdout.write("Creating Device Families...")
155
- DeviceFamilyFactory.create_batch(20)
156
- self.stdout.write("Creating Manufacturers...")
157
- ManufacturerFactory.create_batch(8, using=db_name) # First 8 hard-coded Manufacturers
158
- self.stdout.write("Creating Platforms (with manufacturers)...")
159
- PlatformFactory.create_batch(20, has_manufacturer=True, using=db_name)
160
- self.stdout.write("Creating Platforms (without manufacturers)...")
161
- PlatformFactory.create_batch(5, has_manufacturer=False, using=db_name)
162
- self.stdout.write("Creating SoftwareVersions...")
163
- SoftwareVersionFactory.create_batch(20)
164
- self.stdout.write("Creating SoftwareImageFiles...")
165
- SoftwareImageFileFactory.create_batch(25)
166
- self.stdout.write("Creating Manufacturers without Platforms...")
167
- ManufacturerFactory.create_batch(4, using=db_name) # 4 more hard-coded Manufacturers
168
- self.stdout.write("Creating DeviceTypes...")
169
- DeviceTypeFactory.create_batch(30, using=db_name)
170
- self.stdout.write("Creating Manufacturers without DeviceTypes or Platforms...")
171
- ManufacturerFactory.create_batch(2, using=db_name) # Last 2 hard-coded Manufacturers
172
- self.stdout.write("Creating DeviceRedundancyGroups...")
173
- DeviceRedundancyGroupFactory.create_batch(20, using=db_name)
174
- self.stdout.write("Creating Devices...")
175
- DeviceFactory.create_batch(20, using=db_name)
176
- self.stdout.write("Creating SoftwareVersions with Devices, InventoryItems or VirtualMachines...")
177
- SoftwareVersionFactory.create_batch(5)
178
- self.stdout.write("Creating SoftwareImageFiles without DeviceTypes...")
179
- SoftwareImageFileFactory.create_batch(5)
180
- self.stdout.write("Creating CircuitTypes...")
181
- CircuitTypeFactory.create_batch(40, using=db_name)
182
- self.stdout.write("Creating Providers...")
183
- ProviderFactory.create_batch(20, using=db_name)
184
- self.stdout.write("Creating ProviderNetworks...")
185
- ProviderNetworkFactory.create_batch(20, using=db_name)
186
- self.stdout.write("Creating Circuits...")
187
- CircuitFactory.create_batch(40, using=db_name)
188
- self.stdout.write("Creating Providers without Circuits...")
189
- ProviderFactory.create_batch(20, using=db_name)
190
- self.stdout.write("Creating CircuitTerminations...")
191
- CircuitTerminationFactory.create_batch(2, has_location=True, term_side="A", using=db_name)
192
- CircuitTerminationFactory.create_batch(2, has_location=True, term_side="Z", using=db_name)
193
- CircuitTerminationFactory.create_batch(2, has_location=False, term_side="A", using=db_name)
194
- CircuitTerminationFactory.create_batch(2, has_location=False, term_side="Z", using=db_name)
195
- CircuitTerminationFactory.create_batch(2, has_port_speed=True, has_upstream_speed=False, using=db_name)
196
- CircuitTerminationFactory.create_batch(
197
- size=2,
156
+ _create_batch(
157
+ PrefixFactory,
158
+ 1,
159
+ description=f"(10.{i}.0.0/16 and descendants)",
160
+ prefix=f"10.{i}.0.0/16",
161
+ type=PrefixTypeChoices.TYPE_CONTAINER,
162
+ )
163
+ _create_batch(
164
+ PrefixFactory,
165
+ 1,
166
+ description=f"(2001:db8:0:{i}::/64 and descendants)",
167
+ prefix=f"2001:db8:0:{i}::/64",
168
+ type=PrefixTypeChoices.TYPE_CONTAINER,
169
+ )
170
+ _create_batch(NamespaceFactory, 5, description="without any Prefixes or IPAddresses")
171
+ _create_batch(DeviceFamilyFactory, 20)
172
+ _create_batch(ManufacturerFactory, 8) # First 8 hard-coded Manufacturers
173
+ _create_batch(PlatformFactory, 20, description="with Manufacturers", has_manufacturer=True)
174
+ _create_batch(PlatformFactory, 5, description="without Manufacturers", has_manufacturer=False)
175
+ _create_batch(SoftwareVersionFactory, 20, description="to be usable by Devices")
176
+ _create_batch(SoftwareImageFileFactory, 25, description="to be usable by DeviceTypes")
177
+ _create_batch(ManufacturerFactory, 4, description="without Platforms") # 4 more hard-coded Manufacturers
178
+ _create_batch(DeviceTypeFactory, 30)
179
+ _create_batch(ManufacturerFactory, 2, description="without Platforms or DeviceTypes") # Last 2 hard-coded
180
+ _create_batch(DeviceRedundancyGroupFactory, 20)
181
+ _create_batch(DeviceFactory, 20)
182
+ _create_batch(SoftwareVersionFactory, 5, description="without Devices")
183
+ _create_batch(SoftwareImageFileFactory, 5, description="without DeviceTypes")
184
+ _create_batch(CircuitTypeFactory, 40)
185
+ _create_batch(ProviderFactory, 20, description="to be usable by Circuits")
186
+ _create_batch(ProviderNetworkFactory, 20)
187
+ _create_batch(CircuitFactory, 40)
188
+ _create_batch(ProviderFactory, 20, description="without Circuits")
189
+ # TODO do we really need all of these specifics for CircuitTerminations?
190
+ _create_batch(
191
+ CircuitTerminationFactory, 2, description="with a location, for side A", has_location=True, term_side="A"
192
+ )
193
+ _create_batch(
194
+ CircuitTerminationFactory, 2, description="with a location, for side Z", has_location=True, term_side="Z"
195
+ )
196
+ _create_batch(
197
+ CircuitTerminationFactory,
198
+ 2,
199
+ description="without a location, for side A",
200
+ has_location=False,
201
+ term_side="A",
202
+ )
203
+ _create_batch(
204
+ CircuitTerminationFactory,
205
+ 2,
206
+ description="without a location, for side Z",
207
+ has_location=False,
208
+ term_side="Z",
209
+ )
210
+ _create_batch(
211
+ CircuitTerminationFactory,
212
+ 2,
213
+ description="with port_speed but without upstream_speed",
214
+ has_port_speed=True,
215
+ has_upstream_speed=False,
216
+ )
217
+ _create_batch(
218
+ CircuitTerminationFactory,
219
+ 2,
220
+ description="with a location, port_speed, upstream_speed, xconnect_id, pp_info, and description",
198
221
  has_location=True,
199
222
  has_port_speed=True,
200
223
  has_upstream_speed=True,
201
224
  has_xconnect_id=True,
202
225
  has_pp_info=True,
203
226
  has_description=True,
204
- using=db_name,
205
227
  )
206
- self.stdout.write("Creating ExternalIntegrations...")
207
- ExternalIntegrationFactory.create_batch(20, using=db_name)
208
- self.stdout.write("Creating Controllers with Device or DeviceRedundancyGroups...")
209
- ControllerFactory.create_batch(10)
210
- ControllerManagedDeviceGroupFactory.create_batch(30)
228
+ _create_batch(ExternalIntegrationFactory, 20)
229
+ _create_batch(ControllerFactory, 10, description="with Devices or DeviceRedundancyGroups")
230
+ _create_batch(ControllerManagedDeviceGroupFactory, 5, description="without any Devices")
211
231
  # make sure we have some tenants that have null relationships to make filter tests happy
212
- self.stdout.write("Creating Tenants without Circuits, Locations, IPAddresses, or Prefixes...")
213
- TenantFactory.create_batch(10, using=db_name)
232
+ _create_batch(TenantFactory, 10, description="without any associated objects")
214
233
  # TODO: nautobot.tenancy.tests.test_filters currently calls the following additional factories:
215
- # UserFactory.create_batch(10)
216
- # RackFactory.create_batch(10)
217
- # RackReservationFactory.create_batch(10)
218
- # ClusterTypeFactory.create_batch(10)
219
- # ClusterGroupFactory.create_batch(10)
220
- # ClusterFactory.create_batch(10)
221
- # VirtualMachineFactory.create_batch(10)
234
+ # _create_batch(UserFactory, 10)
235
+ # _create_batch(RackFactory, 10)
236
+ # _create_batch(RackReservationFactory, 10)
237
+ # _create_batch(ClusterTypeFactory, 10)
238
+ # _create_batch(ClusterGroupFactory, 10)
239
+ # _create_batch(ClusterFactory, 10)
240
+ # _create_batch(VirtualMachineFactory, 10)
222
241
  # We need to remove them from there and enable them here instead, but that will require many test updates.
223
242
 
224
- self._output_hash_for_factory_models(
225
- factories=[
226
- CircuitFactory,
227
- CircuitTerminationFactory,
228
- CircuitTypeFactory,
229
- ContactFactory,
230
- ControllerManagedDeviceGroupFactory,
231
- ControllerFactory,
232
- DeviceFactory,
233
- DeviceFamilyFactory,
234
- DeviceRedundancyGroupFactory,
235
- DeviceTypeFactory,
236
- ExternalIntegrationFactory,
237
- IPAddressFactory,
238
- LocationFactory,
239
- LocationTypeFactory,
240
- ManufacturerFactory,
241
- NamespaceFactory,
242
- PlatformFactory,
243
- PrefixFactory,
244
- ProviderFactory,
245
- ProviderNetworkFactory,
246
- RIRFactory,
247
- RoleFactory,
248
- RouteTargetFactory,
249
- SoftwareImageFileFactory,
250
- SoftwareVersionFactory,
251
- StatusFactory,
252
- TagFactory,
253
- TeamFactory,
254
- TenantFactory,
255
- TenantGroupFactory,
256
- VLANFactory,
257
- VLANGroupFactory,
258
- VRFFactory,
259
- ]
260
- )
261
-
262
- def _output_hash_for_factory_models(self, factories):
263
- """Output a hash of the IDs of all objects in the given factories' model.
264
-
265
- Used for identifying factory determinism problems in unit tests. Only prints if GITHUB_ACTIONS environment variable is set to "true".
266
- """
267
- if not is_truthy(os.environ.get("GITHUB_ACTIONS", "false")):
268
- return
269
-
270
- for factory in factories:
271
- model = factory._meta.get_model_class()
272
- model_ids = list(model.objects.order_by("id").values_list("id", flat=True))
273
- sha256_hash = hashlib.sha256(json.dumps(model_ids, cls=DjangoJSONEncoder).encode()).hexdigest()
274
- self.stdout.write(f"SHA256 hash for {model.__name__}: {sha256_hash}")
275
-
276
243
  def handle(self, *args, **options):
277
244
  if options["flush"]:
278
245
  if options["interactive"]:
@@ -298,7 +265,10 @@ Type 'yes' to continue, or 'no' to cancel: """
298
265
  self.stdout.write(self.style.WARNING(f"Loading factory data from file {options['fixture_file']}"))
299
266
  call_command("loaddata", "--database", options["database"], options["fixture_file"])
300
267
  else:
301
- self._generate_factory_data(options["seed"], options["database"])
268
+ print_hashes = options["print_hashes"]
269
+ if is_truthy(os.environ.get("GITHUB_ACTIONS", "false")):
270
+ print_hashes = True
271
+ self._generate_factory_data(options["seed"], options["database"], print_hashes=print_hashes)
302
272
 
303
273
  if options["cache_test_fixtures"]:
304
274
  self.stdout.write(self.style.WARNING(f"Saving factory data to file {options['fixture_file']}"))
@@ -13,6 +13,7 @@ from nautobot.core.constants import CHARFIELD_MAX_LENGTH
13
13
  from nautobot.core.forms import fields, widgets
14
14
  from nautobot.core.models import ordering
15
15
  from nautobot.core.models.managers import TagsManager
16
+ from nautobot.core.models.validators import EnhancedURLValidator
16
17
 
17
18
 
18
19
  class mac_unix_expanded_uppercase(mac_unix_expanded):
@@ -381,6 +382,20 @@ class JSONArrayField(models.JSONField):
381
382
  )
382
383
 
383
384
 
385
+ class LaxURLField(models.URLField):
386
+ """Like models.URLField, but using validators.EnhancedURLValidator and forms.LaxURLField."""
387
+
388
+ default_validators = [EnhancedURLValidator()]
389
+
390
+ def formfield(self, **kwargs):
391
+ return super().formfield(
392
+ **{
393
+ "form_class": fields.LaxURLField,
394
+ **kwargs,
395
+ },
396
+ )
397
+
398
+
384
399
  class TagsField(TaggableManager):
385
400
  """Override FormField method on taggit.managers.TaggableManager to match the Nautobot UI."""
386
401
 
@@ -1,8 +1,10 @@
1
1
  import copy
2
+ import hashlib
2
3
 
3
4
  from django.conf import settings
4
5
  from django.core.management import call_command
5
6
  from django.db import connections
7
+ from django.db.migrations.recorder import MigrationRecorder
6
8
  from django.test.runner import _init_worker, DiscoverRunner, ParallelTestSuite
7
9
  from django.test.utils import get_unique_databases_and_mirrors, NullTimeKeeper, override_settings
8
10
  import yaml
@@ -113,6 +115,14 @@ class NautobotTestRunner(DiscoverRunner):
113
115
  command += ["--seed", settings.TEST_FACTORY_SEED]
114
116
  if self.cache_test_fixtures:
115
117
  command += ["--cache-test-fixtures"]
118
+ # Use the list of applied migrations as a unique hash to keep fixtures from differing
119
+ # branches/releases of Nautobot in separate files.
120
+ hexdigest = hashlib.shake_128(
121
+ ",".join(
122
+ sorted(f"{m.app}.{m.name}" for m in MigrationRecorder.Migration.objects.all())
123
+ ).encode("utf-8")
124
+ ).hexdigest(10)
125
+ command += ["--fixture-file", f"development/factory_dump.{hexdigest}.json"]
116
126
  with time_keeper.timed(f' Pre-populating test database "{alias}" with factory data...'):
117
127
  db_command = [*command, "--database", alias]
118
128
  call_command(*db_command)
@@ -4,6 +4,7 @@ from django import forms as django_forms
4
4
  from django.apps import apps
5
5
  from django.contrib.auth.models import Group
6
6
  from django.contrib.contenttypes.models import ContentType
7
+ from django.core.exceptions import ValidationError
7
8
  from django.db.models import Q
8
9
  from django.http import QueryDict
9
10
  from django.test import TestCase
@@ -11,7 +12,7 @@ from django.test import TestCase
11
12
  from nautobot.circuits import models as circuits_models
12
13
  from nautobot.core import exceptions, forms, settings_funcs
13
14
  from nautobot.core.api import utils as api_utils
14
- from nautobot.core.models import fields as core_fields, utils as models_utils
15
+ from nautobot.core.models import fields as core_fields, utils as models_utils, validators
15
16
  from nautobot.core.utils import data as data_utils, filtering, lookup, requests
16
17
  from nautobot.core.utils.migrations import update_object_change_ct_for_replaced_models
17
18
  from nautobot.dcim import filters as dcim_filters, forms as dcim_forms, models as dcim_models, tables
@@ -372,6 +373,52 @@ class SlugifyFunctionsTest(TestCase):
372
373
  self.assertEqual(core_fields.slugify_dashes_to_underscores(content), expected)
373
374
 
374
375
 
376
+ class LaxURLFieldTest(TestCase):
377
+ """Test LaxURLField and related functionality."""
378
+
379
+ VALID_URLS = [
380
+ "http://example.com",
381
+ "https://local-dns/foo/bar.git", # not supported out-of-the-box by Django, hence our custom classes
382
+ "https://1.1.1.1:8080/",
383
+ "https://[2001:db8::]/",
384
+ ]
385
+ INVALID_URLS = [
386
+ "unknown://example.com/",
387
+ "foo:/",
388
+ "http://file://",
389
+ ]
390
+
391
+ def test_enhanced_url_validator(self):
392
+ for valid in self.VALID_URLS:
393
+ with self.subTest(valid=valid):
394
+ validators.EnhancedURLValidator()(valid)
395
+
396
+ for invalid in self.INVALID_URLS:
397
+ with self.subTest(invalid=invalid):
398
+ with self.assertRaises(django_forms.ValidationError):
399
+ validators.EnhancedURLValidator()(invalid)
400
+
401
+ def test_forms_lax_url_field(self):
402
+ for valid in self.VALID_URLS:
403
+ with self.subTest(valid=valid):
404
+ forms.LaxURLField().clean(valid)
405
+
406
+ for invalid in self.INVALID_URLS:
407
+ with self.subTest(invalid=invalid):
408
+ with self.assertRaises(django_forms.ValidationError):
409
+ forms.LaxURLField().clean(invalid)
410
+
411
+ def test_models_lax_url_field(self):
412
+ for valid in self.VALID_URLS:
413
+ with self.subTest(valid=valid):
414
+ core_fields.LaxURLField().run_validators(valid)
415
+
416
+ for invalid in self.INVALID_URLS:
417
+ with self.subTest(invalid=invalid):
418
+ with self.assertRaises(ValidationError):
419
+ core_fields.LaxURLField().run_validators(invalid)
420
+
421
+
375
422
  class LookupRelatedFunctionTest(TestCase):
376
423
  def test_is_single_choice_field(self):
377
424
  """