nautobot 2.4.0b1__py3-none-any.whl → 2.4.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 (668) hide show
  1. nautobot/apps/__init__.py +1 -1
  2. nautobot/apps/api.py +8 -8
  3. nautobot/apps/change_logging.py +2 -2
  4. nautobot/apps/choices.py +4 -4
  5. nautobot/apps/events.py +3 -3
  6. nautobot/apps/factory.py +2 -2
  7. nautobot/apps/filters.py +1 -1
  8. nautobot/apps/forms.py +20 -20
  9. nautobot/apps/graphql.py +2 -2
  10. nautobot/apps/jobs.py +8 -8
  11. nautobot/apps/models.py +19 -19
  12. nautobot/apps/tables.py +1 -1
  13. nautobot/apps/testing.py +10 -10
  14. nautobot/apps/ui.py +2 -2
  15. nautobot/apps/utils.py +7 -7
  16. nautobot/apps/views.py +7 -7
  17. nautobot/circuits/api/serializers.py +1 -0
  18. nautobot/circuits/api/views.py +4 -8
  19. nautobot/circuits/tables.py +2 -1
  20. nautobot/circuits/templates/circuits/circuit_create.html +1 -7
  21. nautobot/circuits/tests/integration/test_circuits_bulk_operations.py +43 -0
  22. nautobot/circuits/tests/integration/test_relationships.py +1 -1
  23. nautobot/circuits/views.py +3 -3
  24. nautobot/cloud/api/views.py +6 -10
  25. nautobot/cloud/models.py +1 -1
  26. nautobot/cloud/views.py +0 -16
  27. nautobot/core/api/constants.py +11 -0
  28. nautobot/core/api/fields.py +5 -5
  29. nautobot/core/api/filter_backends.py +3 -9
  30. nautobot/core/api/schema.py +13 -2
  31. nautobot/core/api/serializers.py +40 -34
  32. nautobot/core/api/views.py +56 -4
  33. nautobot/core/apps/__init__.py +0 -5
  34. nautobot/core/celery/log.py +4 -4
  35. nautobot/core/celery/schedulers.py +2 -2
  36. nautobot/core/choices.py +2 -2
  37. nautobot/core/events/__init__.py +3 -3
  38. nautobot/core/filters.py +67 -35
  39. nautobot/core/forms/__init__.py +19 -19
  40. nautobot/core/forms/fields.py +14 -11
  41. nautobot/core/forms/forms.py +33 -2
  42. nautobot/core/graphql/types.py +1 -1
  43. nautobot/core/jobs/__init__.py +28 -7
  44. nautobot/core/jobs/bulk_actions.py +285 -0
  45. nautobot/core/jobs/cleanup.py +48 -12
  46. nautobot/core/jobs/groups.py +1 -1
  47. nautobot/core/management/commands/validate_models.py +1 -1
  48. nautobot/core/models/__init__.py +3 -1
  49. nautobot/core/models/query_functions.py +2 -2
  50. nautobot/core/models/tree_queries.py +6 -3
  51. nautobot/core/settings.py +29 -2
  52. nautobot/core/settings.yaml +21 -0
  53. nautobot/core/tables.py +79 -61
  54. nautobot/core/templates/about.html +67 -0
  55. nautobot/core/templates/generic/object_bulk_destroy.html +1 -1
  56. nautobot/core/templates/inc/media.html +3 -0
  57. nautobot/core/templates/inc/nav_menu.html +1 -0
  58. nautobot/core/templates/inc/tenancy_form_panel.html +9 -0
  59. nautobot/core/templates/inc/tenant_table_row.html +11 -0
  60. nautobot/core/templates/nautobot_config.py.j2 +13 -0
  61. nautobot/core/templates/search.html +7 -0
  62. nautobot/core/templates/utilities/render_jinja2.html +1 -1
  63. nautobot/core/templates/utilities/templatetags/tag.html +1 -1
  64. nautobot/core/templates/utilities/theme_preview.html +7 -0
  65. nautobot/core/templatetags/helpers.py +11 -2
  66. nautobot/core/testing/__init__.py +8 -8
  67. nautobot/core/testing/api.py +170 -15
  68. nautobot/core/testing/filters.py +45 -10
  69. nautobot/core/testing/forms.py +2 -0
  70. nautobot/core/testing/integration.py +514 -5
  71. nautobot/core/testing/mixins.py +7 -2
  72. nautobot/core/testing/views.py +44 -29
  73. nautobot/core/tests/integration/test_app_home.py +0 -1
  74. nautobot/core/tests/integration/test_app_navbar.py +0 -1
  75. nautobot/core/tests/integration/test_filters.py +0 -2
  76. nautobot/core/tests/integration/test_home.py +0 -1
  77. nautobot/core/tests/integration/test_navbar.py +0 -1
  78. nautobot/core/tests/integration/test_view_authentication.py +1 -0
  79. nautobot/core/tests/runner.py +1 -1
  80. nautobot/core/tests/test_api.py +98 -1
  81. nautobot/core/tests/test_csv.py +25 -3
  82. nautobot/core/tests/test_filters.py +209 -246
  83. nautobot/core/tests/test_forms.py +1 -0
  84. nautobot/core/tests/test_jobs.py +492 -1
  85. nautobot/core/tests/test_models.py +9 -0
  86. nautobot/core/tests/test_settings_schema.py +7 -0
  87. nautobot/core/tests/test_tables.py +100 -0
  88. nautobot/core/tests/test_utils.py +63 -1
  89. nautobot/core/tests/test_views.py +30 -3
  90. nautobot/core/ui/nav.py +1 -0
  91. nautobot/core/ui/object_detail.py +15 -1
  92. nautobot/core/urls.py +11 -0
  93. nautobot/core/utils/git.py +7 -2
  94. nautobot/core/utils/lookup.py +11 -8
  95. nautobot/core/utils/querysets.py +64 -0
  96. nautobot/core/utils/requests.py +24 -9
  97. nautobot/core/views/__init__.py +42 -0
  98. nautobot/core/views/generic.py +131 -197
  99. nautobot/core/views/mixins.py +136 -41
  100. nautobot/core/views/renderers.py +6 -6
  101. nautobot/core/views/utils.py +2 -2
  102. nautobot/dcim/api/serializers.py +56 -64
  103. nautobot/dcim/api/views.py +47 -113
  104. nautobot/dcim/constants.py +6 -13
  105. nautobot/dcim/factory.py +6 -1
  106. nautobot/dcim/filters/__init__.py +31 -2
  107. nautobot/dcim/forms.py +48 -17
  108. nautobot/dcim/graphql/types.py +2 -2
  109. nautobot/dcim/migrations/0067_controllermanageddevicegroup_tenant.py +25 -0
  110. nautobot/dcim/models/__init__.py +1 -1
  111. nautobot/dcim/models/device_component_templates.py +2 -2
  112. nautobot/dcim/models/device_components.py +22 -20
  113. nautobot/dcim/models/devices.py +10 -1
  114. nautobot/dcim/models/locations.py +3 -3
  115. nautobot/dcim/models/power.py +6 -5
  116. nautobot/dcim/models/racks.py +4 -4
  117. nautobot/dcim/tables/__init__.py +3 -3
  118. nautobot/dcim/tables/devices.py +9 -6
  119. nautobot/dcim/tables/devicetypes.py +2 -2
  120. nautobot/dcim/tables/racks.py +1 -1
  121. nautobot/dcim/templates/dcim/cable.html +1 -1
  122. nautobot/dcim/templates/dcim/controller_create.html +1 -7
  123. nautobot/dcim/templates/dcim/controller_retrieve.html +1 -9
  124. nautobot/dcim/templates/dcim/controllermanageddevicegroup_create.html +2 -0
  125. nautobot/dcim/templates/dcim/controllermanageddevicegroup_retrieve.html +5 -0
  126. nautobot/dcim/templates/dcim/device/base.html +1 -1
  127. nautobot/dcim/templates/dcim/device.html +3 -11
  128. nautobot/dcim/templates/dcim/device_component.html +1 -1
  129. nautobot/dcim/templates/dcim/device_edit.html +36 -37
  130. nautobot/dcim/templates/dcim/devicetype.html +1 -1
  131. nautobot/dcim/templates/dcim/location.html +2 -10
  132. nautobot/dcim/templates/dcim/location_edit.html +1 -7
  133. nautobot/dcim/templates/dcim/locationtype.html +1 -1
  134. nautobot/dcim/templates/dcim/locationtype_retrieve.html +1 -1
  135. nautobot/dcim/templates/dcim/manufacturer.html +1 -1
  136. nautobot/dcim/templates/dcim/platform.html +1 -1
  137. nautobot/dcim/templates/dcim/powerfeed.html +1 -1
  138. nautobot/dcim/templates/dcim/powerpanel.html +1 -1
  139. nautobot/dcim/templates/dcim/rack.html +2 -10
  140. nautobot/dcim/templates/dcim/rack_edit.html +1 -7
  141. nautobot/dcim/templates/dcim/rackgroup.html +1 -1
  142. nautobot/dcim/templates/dcim/rackreservation.html +3 -11
  143. nautobot/dcim/templates/dcim/virtualchassis.html +1 -1
  144. nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +1 -9
  145. nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +1 -7
  146. nautobot/dcim/tests/integration/test_controller.py +62 -0
  147. nautobot/dcim/tests/integration/test_controller_managed_device_group.py +71 -0
  148. nautobot/dcim/tests/integration/test_device_bulk_operations.py +30 -0
  149. nautobot/dcim/tests/integration/test_location_bulk_operations.py +43 -0
  150. nautobot/dcim/tests/test_api.py +16 -5
  151. nautobot/dcim/tests/test_filters.py +33 -0
  152. nautobot/dcim/tests/test_forms.py +51 -2
  153. nautobot/dcim/tests/test_graphql.py +52 -0
  154. nautobot/dcim/tests/test_jobs.py +118 -0
  155. nautobot/dcim/tests/test_models.py +52 -9
  156. nautobot/dcim/tests/test_views.py +30 -84
  157. nautobot/dcim/views.py +13 -28
  158. nautobot/extras/api/customfields.py +2 -2
  159. nautobot/extras/api/serializers.py +123 -85
  160. nautobot/extras/api/views.py +33 -30
  161. nautobot/extras/constants.py +3 -0
  162. nautobot/extras/datasources/git.py +125 -0
  163. nautobot/extras/filters/__init__.py +8 -6
  164. nautobot/extras/forms/base.py +2 -2
  165. nautobot/extras/forms/forms.py +139 -31
  166. nautobot/extras/forms/mixins.py +14 -6
  167. nautobot/extras/group_sync.py +3 -3
  168. nautobot/extras/health_checks.py +1 -2
  169. nautobot/extras/jobs.py +85 -18
  170. nautobot/extras/managers.py +3 -1
  171. nautobot/extras/migrations/0018_joblog_data_migration.py +7 -9
  172. nautobot/extras/migrations/0120_job_is_singleton_job_is_singleton_override.py +22 -0
  173. nautobot/extras/migrations/0121_alter_team_contacts.py +17 -0
  174. nautobot/extras/migrations/0122_add_graphqlquery_owner_content_type.py +34 -0
  175. nautobot/extras/models/__init__.py +1 -1
  176. nautobot/extras/models/contacts.py +1 -1
  177. nautobot/extras/models/customfields.py +41 -23
  178. nautobot/extras/models/datasources.py +85 -0
  179. nautobot/extras/models/groups.py +11 -9
  180. nautobot/extras/models/jobs.py +23 -4
  181. nautobot/extras/models/models.py +17 -2
  182. nautobot/extras/models/relationships.py +17 -5
  183. nautobot/extras/plugins/__init__.py +13 -2
  184. nautobot/extras/plugins/marketplace_manifest.yml +84 -79
  185. nautobot/extras/plugins/tables.py +16 -14
  186. nautobot/extras/plugins/views.py +65 -69
  187. nautobot/extras/registry.py +1 -1
  188. nautobot/extras/secrets/__init__.py +2 -2
  189. nautobot/extras/signals.py +15 -1
  190. nautobot/extras/tables.py +7 -5
  191. nautobot/extras/templates/extras/computedfield.html +1 -1
  192. nautobot/extras/templates/extras/configcontext.html +1 -1
  193. nautobot/extras/templates/extras/configcontextschema.html +1 -1
  194. nautobot/extras/templates/extras/customfield.html +1 -1
  195. nautobot/extras/templates/extras/customlink.html +1 -1
  196. nautobot/extras/templates/extras/dynamicgroup.html +2 -10
  197. nautobot/extras/templates/extras/exporttemplate.html +1 -1
  198. nautobot/extras/templates/extras/gitrepository.html +1 -1
  199. nautobot/extras/templates/extras/graphqlquery.html +1 -1
  200. nautobot/extras/templates/extras/job_detail.html +17 -1
  201. nautobot/extras/templates/extras/job_edit.html +1 -0
  202. nautobot/extras/templates/extras/jobbutton_retrieve.html +1 -1
  203. nautobot/extras/templates/extras/jobhook.html +1 -1
  204. nautobot/extras/templates/extras/jobqueue_retrieve.html +1 -9
  205. nautobot/extras/templates/extras/jobresult.html +1 -1
  206. nautobot/extras/templates/extras/marketplace.html +29 -11
  207. nautobot/extras/templates/extras/objectchange.html +1 -1
  208. nautobot/extras/templates/extras/plugin_detail.html +33 -16
  209. nautobot/extras/templates/extras/plugins_tiles.html +21 -10
  210. nautobot/extras/templates/extras/relationship.html +1 -63
  211. nautobot/extras/templates/extras/role_retrieve.html +1 -1
  212. nautobot/extras/templates/extras/scheduledjob.html +1 -1
  213. nautobot/extras/templates/extras/secret.html +1 -1
  214. nautobot/extras/templates/extras/secretsgroup.html +1 -1
  215. nautobot/extras/templates/extras/status.html +1 -1
  216. nautobot/extras/templates/extras/tag.html +1 -1
  217. nautobot/extras/templates/extras/webhook.html +1 -1
  218. nautobot/extras/templatetags/job_buttons.py +4 -4
  219. nautobot/extras/test_jobs/api_test_job.py +1 -1
  220. nautobot/extras/test_jobs/atomic_transaction.py +2 -2
  221. nautobot/extras/test_jobs/dry_run.py +1 -1
  222. nautobot/extras/test_jobs/fail.py +5 -5
  223. nautobot/extras/test_jobs/file_output.py +1 -1
  224. nautobot/extras/test_jobs/file_upload_fail.py +1 -1
  225. nautobot/extras/test_jobs/file_upload_pass.py +1 -1
  226. nautobot/extras/test_jobs/ipaddress_vars.py +3 -1
  227. nautobot/extras/test_jobs/jobs_module/jobs_submodule/jobs.py +1 -1
  228. nautobot/extras/test_jobs/location_with_custom_field.py +1 -1
  229. nautobot/extras/test_jobs/log_redaction.py +1 -1
  230. nautobot/extras/test_jobs/log_skip_db_logging.py +1 -1
  231. nautobot/extras/test_jobs/modify_db.py +1 -1
  232. nautobot/extras/test_jobs/object_var_optional.py +1 -1
  233. nautobot/extras/test_jobs/object_var_required.py +1 -1
  234. nautobot/extras/test_jobs/object_vars.py +1 -1
  235. nautobot/extras/test_jobs/pass.py +3 -3
  236. nautobot/extras/test_jobs/profiling.py +1 -1
  237. nautobot/extras/test_jobs/relative_import.py +3 -3
  238. nautobot/extras/test_jobs/singleton.py +16 -0
  239. nautobot/extras/test_jobs/soft_time_limit_greater_than_time_limit.py +1 -1
  240. nautobot/extras/test_jobs/task_queues.py +1 -1
  241. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_interfaces.gql +8 -0
  242. nautobot/extras/tests/git_data/01-valid-files/graphql_queries/device_names.gql +5 -0
  243. nautobot/extras/tests/git_data/02-invalid-files/graphql_queries/bad_device_names.gql +5 -0
  244. nautobot/extras/tests/git_helper.py +9 -1
  245. nautobot/extras/tests/integration/__init__.py +29 -16
  246. nautobot/extras/tests/integration/test_plugin_banner.py +0 -2
  247. nautobot/extras/tests/test_api.py +19 -13
  248. nautobot/extras/tests/test_customfields.py +50 -52
  249. nautobot/extras/tests/test_datasources.py +29 -1
  250. nautobot/extras/tests/test_dynamicgroups.py +1 -1
  251. nautobot/extras/tests/test_filters.py +6 -6
  252. nautobot/extras/tests/test_forms.py +33 -1
  253. nautobot/extras/tests/test_jobs.py +178 -32
  254. nautobot/extras/tests/test_models.py +299 -10
  255. nautobot/extras/tests/test_plugins.py +62 -9
  256. nautobot/extras/tests/test_relationships.py +120 -9
  257. nautobot/extras/tests/test_utils.py +22 -1
  258. nautobot/extras/tests/test_views.py +56 -194
  259. nautobot/extras/utils.py +20 -10
  260. nautobot/extras/views.py +85 -110
  261. nautobot/ipam/api/fields.py +3 -3
  262. nautobot/ipam/api/serializers.py +41 -33
  263. nautobot/ipam/api/views.py +68 -117
  264. nautobot/ipam/factory.py +1 -1
  265. nautobot/ipam/filters.py +3 -2
  266. nautobot/ipam/lookups.py +101 -62
  267. nautobot/ipam/models.py +74 -18
  268. nautobot/ipam/querysets.py +2 -2
  269. nautobot/ipam/tables.py +25 -9
  270. nautobot/ipam/templates/ipam/ipaddress.html +2 -10
  271. nautobot/ipam/templates/ipam/ipaddress_bulk_add.html +1 -7
  272. nautobot/ipam/templates/ipam/ipaddress_edit.html +1 -7
  273. nautobot/ipam/templates/ipam/prefix.html +2 -10
  274. nautobot/ipam/templates/ipam/prefix_edit.html +1 -7
  275. nautobot/ipam/templates/ipam/rir.html +1 -1
  276. nautobot/ipam/templates/ipam/routetarget.html +1 -1
  277. nautobot/ipam/templates/ipam/service.html +1 -1
  278. nautobot/ipam/templates/ipam/vlan.html +2 -10
  279. nautobot/ipam/templates/ipam/vlan_edit.html +1 -7
  280. nautobot/ipam/templates/ipam/vlangroup.html +1 -1
  281. nautobot/ipam/templates/ipam/vrf.html +1 -1
  282. nautobot/ipam/templates/ipam/vrf_edit.html +1 -7
  283. nautobot/ipam/tests/test_api.py +436 -3
  284. nautobot/ipam/tests/test_forms.py +49 -47
  285. nautobot/ipam/tests/test_migrations.py +30 -30
  286. nautobot/ipam/tests/test_models.py +119 -34
  287. nautobot/ipam/tests/test_querysets.py +63 -1
  288. nautobot/ipam/tests/test_utils.py +41 -2
  289. nautobot/ipam/tests/test_views.py +3 -0
  290. nautobot/ipam/utils/__init__.py +54 -17
  291. nautobot/ipam/views.py +61 -87
  292. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.css.map +1 -1
  293. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap-theme.min.css.map +1 -1
  294. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css +40 -2
  295. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.css.map +1 -1
  296. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css +1 -1
  297. nautobot/project-static/bootstrap-3.4.1-dist/css/bootstrap.min.css.map +1 -1
  298. nautobot/project-static/docs/404.html +131 -14
  299. nautobot/project-static/docs/apps/index.html +131 -14
  300. nautobot/project-static/docs/apps/nautobot-apps.html +132 -16
  301. nautobot/project-static/docs/assets/_mkdocstrings.css +25 -1
  302. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js → bundle.60a45f97.min.js} +2 -2
  303. nautobot/project-static/docs/assets/javascripts/{bundle.83f73b43.min.js.map → bundle.60a45f97.min.js.map} +2 -2
  304. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js → search.f8cc74c7.min.js} +1 -1
  305. nautobot/project-static/docs/assets/javascripts/workers/{search.6ce7567c.min.js.map → search.f8cc74c7.min.js.map} +1 -1
  306. nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css → main.a40c8224.min.css} +1 -1
  307. nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +147 -20
  308. nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +144 -17
  309. nautobot/project-static/docs/code-reference/nautobot/apps/api.html +459 -132
  310. nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +175 -28
  311. nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +180 -31
  312. nautobot/project-static/docs/code-reference/nautobot/apps/config.html +138 -16
  313. nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +137 -15
  314. nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +164 -27
  315. nautobot/project-static/docs/code-reference/nautobot/apps/events.html +187 -38
  316. nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +193 -31
  317. nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +216 -48
  318. nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +324 -75
  319. nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +666 -175
  320. nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +194 -46
  321. nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +538 -177
  322. nautobot/project-static/docs/code-reference/nautobot/apps/models.html +578 -221
  323. nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +145 -18
  324. nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +156 -25
  325. nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +492 -65
  326. nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +705 -215
  327. nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +943 -422
  328. nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +144 -17
  329. nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +619 -200
  330. nautobot/project-static/docs/code-reference/nautobot/apps/views.html +474 -159
  331. nautobot/project-static/docs/development/apps/api/configuration-view.html +131 -14
  332. nautobot/project-static/docs/development/apps/api/database-backend-config.html +131 -14
  333. nautobot/project-static/docs/development/apps/api/models/django-admin.html +131 -14
  334. nautobot/project-static/docs/development/apps/api/models/global-search.html +131 -14
  335. nautobot/project-static/docs/development/apps/api/models/graphql.html +131 -14
  336. nautobot/project-static/docs/development/apps/api/models/index.html +131 -14
  337. nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +131 -14
  338. nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +131 -14
  339. nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +131 -14
  340. nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +131 -14
  341. nautobot/project-static/docs/development/apps/api/platform-features/index.html +131 -14
  342. nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +131 -14
  343. nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +131 -14
  344. nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +131 -14
  345. nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +131 -14
  346. nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +153 -17
  347. nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +131 -14
  348. nautobot/project-static/docs/development/apps/api/prometheus.html +131 -14
  349. nautobot/project-static/docs/development/apps/api/setup.html +131 -14
  350. nautobot/project-static/docs/development/apps/api/testing.html +131 -14
  351. nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +131 -14
  352. nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +131 -14
  353. nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +131 -14
  354. nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +131 -14
  355. nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +131 -14
  356. nautobot/project-static/docs/development/apps/api/views/base-template.html +131 -14
  357. nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +131 -14
  358. nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +131 -14
  359. nautobot/project-static/docs/development/apps/api/views/help-documentation.html +131 -14
  360. nautobot/project-static/docs/development/apps/api/views/index.html +131 -14
  361. nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +131 -14
  362. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +131 -14
  363. nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +131 -14
  364. nautobot/project-static/docs/development/apps/api/views/notes.html +131 -14
  365. nautobot/project-static/docs/development/apps/api/views/rest-api.html +137 -16
  366. nautobot/project-static/docs/development/apps/api/views/urls.html +131 -14
  367. nautobot/project-static/docs/development/apps/index.html +131 -14
  368. nautobot/project-static/docs/development/apps/migration/code-updates.html +131 -14
  369. nautobot/project-static/docs/development/apps/migration/dependency-updates.html +131 -14
  370. nautobot/project-static/docs/development/apps/migration/from-v1.html +131 -14
  371. nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +131 -14
  372. nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +131 -14
  373. nautobot/project-static/docs/development/apps/migration/model-updates/global.html +131 -14
  374. nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +131 -14
  375. nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +135 -18
  376. nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +131 -14
  377. nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +297 -25
  378. nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +131 -14
  379. nautobot/project-static/docs/development/apps/porting-from-netbox.html +131 -14
  380. nautobot/project-static/docs/development/core/application-registry.html +131 -14
  381. nautobot/project-static/docs/development/core/best-practices.html +131 -14
  382. nautobot/project-static/docs/development/core/bootstrap-ui.html +131 -14
  383. nautobot/project-static/docs/development/core/caching.html +131 -14
  384. nautobot/project-static/docs/development/core/controllers.html +131 -14
  385. nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +158 -84
  386. nautobot/project-static/docs/development/core/generic-views.html +131 -14
  387. nautobot/project-static/docs/development/core/getting-started.html +334 -234
  388. nautobot/project-static/docs/development/core/homepage.html +134 -17
  389. nautobot/project-static/docs/development/core/index.html +131 -14
  390. nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9829 -0
  391. nautobot/project-static/docs/development/core/model-checklist.html +141 -22
  392. nautobot/project-static/docs/development/core/model-features.html +131 -14
  393. nautobot/project-static/docs/development/core/natural-keys.html +131 -14
  394. nautobot/project-static/docs/development/core/navigation-menu.html +131 -14
  395. nautobot/project-static/docs/development/core/release-checklist.html +134 -17
  396. nautobot/project-static/docs/development/core/role-internals.html +131 -14
  397. nautobot/project-static/docs/development/core/settings.html +131 -14
  398. nautobot/project-static/docs/development/core/style-guide.html +134 -17
  399. nautobot/project-static/docs/development/core/templates.html +132 -15
  400. nautobot/project-static/docs/development/core/testing.html +131 -14
  401. nautobot/project-static/docs/development/core/ui-component-framework.html +454 -283
  402. nautobot/project-static/docs/development/core/user-preferences.html +131 -14
  403. nautobot/project-static/docs/development/index.html +131 -14
  404. nautobot/project-static/docs/development/jobs/index.html +301 -132
  405. nautobot/project-static/docs/development/jobs/migration/from-v1.html +131 -14
  406. nautobot/project-static/docs/index.html +139 -33
  407. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit.png +0 -0
  408. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_edit_button.png +0 -0
  409. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_nav.png +0 -0
  410. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_list_view.png +0 -0
  411. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue.png +0 -0
  412. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_add.png +0 -0
  413. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_queue_config.png +0 -0
  414. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_completed.png +0 -0
  415. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_nav.png +0 -0
  416. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_result_pending.png +0 -0
  417. nautobot/project-static/docs/media/development/core/kubernetes/k8s_job_run_form.png +0 -0
  418. nautobot/project-static/docs/media/development/core/kubernetes/k8s_nautobot_login.png +0 -0
  419. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_job.png +0 -0
  420. nautobot/project-static/docs/media/development/core/kubernetes/k8s_run_scheduled_job_form.png +0 -0
  421. nautobot/project-static/docs/media/development/core/kubernetes/k8s_scheduled_job_result.png +0 -0
  422. nautobot/project-static/docs/media/development/core/ui-component-framework/buttons-example.png +0 -0
  423. nautobot/project-static/docs/media/development/core/ui-component-framework/cluster-type-before-after-example.png +0 -0
  424. nautobot/project-static/docs/media/development/core/ui-component-framework/object-fields-panel-example_2.png +0 -0
  425. nautobot/project-static/docs/media/development/core/ui-component-framework/stats-panel-example-code.png +0 -0
  426. nautobot/project-static/docs/objects.inv +0 -0
  427. nautobot/project-static/docs/overview/application_stack.html +132 -17
  428. nautobot/project-static/docs/overview/design_philosophy.html +131 -14
  429. nautobot/project-static/docs/release-notes/index.html +137 -22
  430. nautobot/project-static/docs/release-notes/version-1.0.html +319 -203
  431. nautobot/project-static/docs/release-notes/version-1.1.html +316 -200
  432. nautobot/project-static/docs/release-notes/version-1.2.html +391 -275
  433. nautobot/project-static/docs/release-notes/version-1.3.html +417 -301
  434. nautobot/project-static/docs/release-notes/version-1.4.html +502 -387
  435. nautobot/project-static/docs/release-notes/version-1.5.html +690 -576
  436. nautobot/project-static/docs/release-notes/version-1.6.html +989 -457
  437. nautobot/project-static/docs/release-notes/version-2.0.html +613 -499
  438. nautobot/project-static/docs/release-notes/version-2.1.html +448 -334
  439. nautobot/project-static/docs/release-notes/version-2.2.html +441 -327
  440. nautobot/project-static/docs/release-notes/version-2.3.html +1171 -451
  441. nautobot/project-static/docs/release-notes/version-2.4.html +800 -111
  442. nautobot/project-static/docs/requirements.txt +2 -2
  443. nautobot/project-static/docs/search/search_index.json +1 -1
  444. nautobot/project-static/docs/sitemap.xml +303 -287
  445. nautobot/project-static/docs/sitemap.xml.gz +0 -0
  446. nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +131 -14
  447. nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +131 -14
  448. nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +133 -16
  449. nautobot/project-static/docs/user-guide/administration/configuration/index.html +131 -14
  450. nautobot/project-static/docs/user-guide/administration/configuration/redis.html +131 -14
  451. nautobot/project-static/docs/user-guide/administration/configuration/settings.html +195 -18
  452. nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +131 -14
  453. nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +134 -17
  454. nautobot/project-static/docs/user-guide/administration/guides/docker.html +131 -14
  455. nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +131 -14
  456. nautobot/project-static/docs/user-guide/administration/guides/permissions.html +131 -14
  457. nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +131 -14
  458. nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +131 -14
  459. nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +131 -14
  460. nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +133 -16
  461. nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +131 -14
  462. nautobot/project-static/docs/user-guide/administration/installation/app-install.html +131 -14
  463. nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +131 -14
  464. nautobot/project-static/docs/user-guide/administration/installation/http-server.html +151 -18
  465. nautobot/project-static/docs/user-guide/administration/installation/index.html +131 -14
  466. nautobot/project-static/docs/user-guide/administration/installation/install_system.html +132 -15
  467. nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +131 -14
  468. nautobot/project-static/docs/user-guide/administration/installation/services.html +131 -14
  469. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +131 -14
  470. nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +131 -14
  471. nautobot/project-static/docs/user-guide/administration/security/index.html +9420 -0
  472. nautobot/project-static/docs/user-guide/administration/security/notices.html +9843 -0
  473. nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +131 -14
  474. nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +131 -14
  475. nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +131 -14
  476. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +131 -14
  477. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +131 -14
  478. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +131 -14
  479. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +131 -14
  480. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +134 -18
  481. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +131 -14
  482. nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +131 -14
  483. nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +131 -14
  484. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +131 -14
  485. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +131 -14
  486. nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +131 -14
  487. nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +131 -14
  488. nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +131 -14
  489. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +131 -14
  490. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +131 -14
  491. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +131 -14
  492. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +131 -14
  493. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +131 -14
  494. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +131 -14
  495. nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +131 -14
  496. nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +131 -14
  497. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +131 -14
  498. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +131 -14
  499. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +131 -14
  500. nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +131 -14
  501. nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +131 -14
  502. nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +131 -14
  503. nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +131 -14
  504. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +131 -14
  505. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +131 -14
  506. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +131 -14
  507. nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +131 -14
  508. nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +131 -14
  509. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +131 -14
  510. nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +131 -14
  511. nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +131 -14
  512. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +131 -14
  513. nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +131 -14
  514. nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +131 -14
  515. nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +131 -14
  516. nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +131 -14
  517. nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +131 -14
  518. nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +131 -14
  519. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +131 -14
  520. nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +131 -14
  521. nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +131 -14
  522. nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +131 -14
  523. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +131 -14
  524. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +131 -14
  525. nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +131 -14
  526. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +131 -14
  527. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +131 -14
  528. nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +131 -14
  529. nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +131 -14
  530. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +131 -14
  531. nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +131 -14
  532. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +131 -14
  533. nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +131 -14
  534. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +131 -14
  535. nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +131 -14
  536. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +131 -14
  537. nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +131 -14
  538. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +135 -22
  539. nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +131 -14
  540. nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +131 -14
  541. nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +131 -14
  542. nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +131 -14
  543. nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +131 -14
  544. nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +131 -14
  545. nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +131 -14
  546. nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +131 -14
  547. nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +131 -14
  548. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +131 -14
  549. nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +131 -14
  550. nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +131 -14
  551. nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +134 -17
  552. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +131 -14
  553. nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +131 -14
  554. nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +131 -14
  555. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +131 -14
  556. nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +131 -14
  557. nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +131 -14
  558. nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +131 -14
  559. nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +131 -14
  560. nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +131 -14
  561. nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +131 -14
  562. nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +131 -14
  563. nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +131 -14
  564. nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +131 -14
  565. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +131 -14
  566. nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +131 -14
  567. nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +134 -17
  568. nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +131 -14
  569. nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +131 -14
  570. nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +131 -14
  571. nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +131 -14
  572. nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +131 -14
  573. nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +131 -14
  574. nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +236 -34
  575. nautobot/project-static/docs/user-guide/feature-guides/graphql.html +131 -14
  576. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/central-mode.png +0 -0
  577. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-add.png +0 -0
  578. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-1.png +0 -0
  579. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/device-group-create-2.png +0 -0
  580. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-add.png +0 -0
  581. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/radio-profile-create.png +0 -0
  582. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-add.png +0 -0
  583. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/supported-data-rate-create.png +0 -0
  584. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-add.png +0 -0
  585. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-1.png +0 -0
  586. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-controller-create-2.png +0 -0
  587. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-add.png +0 -0
  588. nautobot/project-static/docs/user-guide/feature-guides/images/wireless/wireless-network-create.png +0 -0
  589. nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +131 -14
  590. nautobot/project-static/docs/user-guide/feature-guides/relationships.html +131 -14
  591. nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +134 -17
  592. nautobot/project-static/docs/{development/core/local-k8s.html → user-guide/feature-guides/wireless-networks-and-controllers.html} +632 -566
  593. nautobot/project-static/docs/user-guide/index.html +131 -14
  594. nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +131 -14
  595. nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +135 -18
  596. nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +131 -14
  597. nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +131 -14
  598. nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +131 -14
  599. nautobot/project-static/docs/user-guide/platform-functionality/events.html +131 -14
  600. nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +131 -14
  601. nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +131 -14
  602. nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +451 -16
  603. nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +131 -14
  604. nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +131 -14
  605. nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +131 -14
  606. nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +135 -17
  607. nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +131 -14
  608. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +134 -17
  609. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +131 -14
  610. nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +131 -14
  611. nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9797 -0
  612. nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +131 -14
  613. nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +131 -14
  614. nautobot/project-static/docs/user-guide/platform-functionality/note.html +131 -14
  615. nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +131 -14
  616. nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +132 -15
  617. nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +131 -14
  618. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +131 -14
  619. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +179 -35
  620. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +159 -15
  621. nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +131 -14
  622. nautobot/project-static/docs/user-guide/platform-functionality/role.html +131 -14
  623. nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +131 -14
  624. nautobot/project-static/docs/user-guide/platform-functionality/secret.html +131 -14
  625. nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +131 -14
  626. nautobot/project-static/docs/user-guide/platform-functionality/status.html +131 -14
  627. nautobot/project-static/docs/user-guide/platform-functionality/tag.html +131 -14
  628. nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +131 -14
  629. nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +131 -14
  630. nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +131 -14
  631. nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +131 -14
  632. nautobot/project-static/js/forms.js +1 -1
  633. nautobot/tenancy/api/views.py +9 -13
  634. nautobot/tenancy/templates/tenancy/tenant.html +1 -2
  635. nautobot/tenancy/templates/tenancy/tenantgroup.html +1 -1
  636. nautobot/tenancy/views.py +4 -2
  637. nautobot/users/admin.py +1 -1
  638. nautobot/users/api/serializers.py +5 -4
  639. nautobot/users/api/views.py +3 -3
  640. nautobot/virtualization/api/serializers.py +4 -4
  641. nautobot/virtualization/api/views.py +5 -24
  642. nautobot/virtualization/filters.py +20 -3
  643. nautobot/virtualization/models.py +1 -1
  644. nautobot/virtualization/tables.py +2 -2
  645. nautobot/virtualization/templates/virtualization/cluster.html +1 -1
  646. nautobot/virtualization/templates/virtualization/cluster_edit.html +1 -7
  647. nautobot/virtualization/templates/virtualization/clustergroup.html +1 -1
  648. nautobot/virtualization/templates/virtualization/clustertype.html +1 -1
  649. nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -10
  650. nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -8
  651. nautobot/virtualization/templates/virtualization/vminterface.html +1 -1
  652. nautobot/virtualization/tests/test_filters.py +17 -0
  653. nautobot/wireless/filters.py +2 -2
  654. nautobot/wireless/forms.py +1 -1
  655. nautobot/wireless/templates/wireless/wirelessnetwork_retrieve.html +1 -9
  656. nautobot/wireless/tests/integration/__init__.py +0 -0
  657. nautobot/wireless/tests/integration/test_radio_profile.py +42 -0
  658. nautobot/wireless/tests/test_filters.py +29 -1
  659. nautobot/wireless/tests/test_views.py +22 -1
  660. nautobot/wireless/views.py +0 -10
  661. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/METADATA +9 -9
  662. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/RECORD +667 -610
  663. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/WHEEL +1 -1
  664. nautobot/core/fixtures/user-data.json +0 -59
  665. /nautobot/project-static/docs/assets/stylesheets/{main.6f8fc17f.min.css.map → main.a40c8224.min.css.map} +0 -0
  666. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/LICENSE.txt +0 -0
  667. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/NOTICE +0 -0
  668. {nautobot-2.4.0b1.dist-info → nautobot-2.4.2.dist-info}/entry_points.txt +0 -0
@@ -5,11 +5,13 @@ from typing import Optional, Sequence, Union
5
5
  from django.conf import settings
6
6
  from django.contrib.contenttypes.fields import GenericForeignKey
7
7
  from django.contrib.contenttypes.models import ContentType
8
+ from django.db import connections, DEFAULT_DB_ALIAS
8
9
  from django.db.models import ForeignKey, ManyToManyField, QuerySet
9
10
  from django.test import override_settings, tag
11
+ from django.test.utils import CaptureQueriesContext
10
12
  from django.urls import reverse
11
13
  from django.utils.text import slugify
12
- from rest_framework import status
14
+ from rest_framework import serializers, status
13
15
  from rest_framework.relations import ManyRelatedField
14
16
  from rest_framework.test import APITransactionTestCase as _APITransactionTestCase
15
17
 
@@ -94,6 +96,15 @@ class APITestCase(views.ModelTestCase):
94
96
  for verboten in self.VERBOTEN_STRINGS:
95
97
  self.assertNotIn(verboten, response_raw_content)
96
98
 
99
+ def get_m2m_fields(self):
100
+ """Get serializer field names that are many-to-many or one-to-many and thus affected by ?exclude_m2m=true."""
101
+ serializer_class = get_serializer_for_model(self.model)
102
+ m2m_fields = []
103
+ for field_name, field_instance in serializer_class().fields.items():
104
+ if isinstance(field_instance, (serializers.ManyRelatedField, serializers.ListSerializer)):
105
+ m2m_fields.append(field_name)
106
+ return m2m_fields
107
+
97
108
 
98
109
  @tag("unit")
99
110
  class APIViewTestCases:
@@ -231,7 +242,7 @@ class APIViewTestCases:
231
242
  def get_depth_fields(self):
232
243
  """Get a list of model fields that could be tested with the ?depth query parameter"""
233
244
  depth_fields = []
234
- for field in self.model._meta.fields:
245
+ for field in self.model._meta.get_fields():
235
246
  if not field.name.startswith("_"):
236
247
  if isinstance(field, (ForeignKey, GenericForeignKey, ManyToManyField, core_fields.TagsField)) and (
237
248
  # we represent content-types as "app_label.modelname" rather than as FKs
@@ -240,6 +251,9 @@ class APIViewTestCases:
240
251
  and not (field.name == "user" and self.model == users_models.Token)
241
252
  ):
242
253
  depth_fields.append(field.name)
254
+ serializer_class = get_serializer_for_model(self.model)
255
+ serializer = serializer_class()
256
+ depth_fields = [field_name for field_name in depth_fields if field_name in serializer.fields]
243
257
  return depth_fields
244
258
 
245
259
  @override_settings(EXEMPT_VIEW_PERMISSIONS=["*"])
@@ -269,9 +283,12 @@ class APIViewTestCases:
269
283
  GET a list of objects using the "?depth=0" parameter.
270
284
  """
271
285
  depth_fields = self.get_depth_fields()
286
+ m2m_fields = self.get_m2m_fields()
272
287
  self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
273
- url = f"{self._get_list_url()}?depth=0"
274
- response = self.client.get(url, **self.header)
288
+ list_url = f"{self._get_list_url()}?depth=0"
289
+ with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
290
+ response = self.client.get(list_url, **self.header)
291
+ base_num_queries = len(cqc)
275
292
 
276
293
  self.assertHttpStatus(response, status.HTTP_200_OK)
277
294
  self.assertIsInstance(response.data, dict)
@@ -280,15 +297,23 @@ class APIViewTestCases:
280
297
  self.assert_no_verboten_content(response)
281
298
 
282
299
  for response_data in response.data["results"]:
300
+ for field in m2m_fields:
301
+ self.assertIn(field, response_data)
302
+ self.assertIsInstance(response_data[field], list)
283
303
  for field in depth_fields:
284
304
  self.assertIn(field, response_data)
285
305
  if isinstance(response_data[field], list):
286
306
  for entry in response_data[field]:
287
307
  self.assertIsInstance(entry, dict)
288
- self.assertTrue(is_uuid(entry["id"]))
308
+ if entry["object_type"] in ["auth.group"]:
309
+ self.assertIsInstance(entry["id"], int)
310
+ else:
311
+ self.assertTrue(is_uuid(entry["id"]))
312
+ self.assertEqual(len(entry.keys()), 3) # just id/object_type/url
289
313
  else:
290
314
  if response_data[field] is not None:
291
315
  self.assertIsInstance(response_data[field], dict)
316
+ self.assertEqual(len(response_data[field].keys()), 3) # just id/object_type/url
292
317
  url = response_data[field]["url"]
293
318
  pk = response_data[field]["id"]
294
319
  object_type = response_data[field]["object_type"]
@@ -296,22 +321,67 @@ class APIViewTestCases:
296
321
  # URL ending in the UUID of the relevant object:
297
322
  # http://nautobot.example.com/api/circuits/providers/<uuid>/
298
323
  # ^^^^^^
299
- self.assertTrue(is_uuid(url.split("/")[-2]))
300
- self.assertTrue(is_uuid(pk))
324
+ if object_type in ["auth.group"]:
325
+ self.assertIsInstance(url.split("/")[-2], int)
326
+ self.assertIsInstance(pk, int)
327
+ else:
328
+ self.assertTrue(is_uuid(url.split("/")[-2]))
329
+ self.assertTrue(is_uuid(pk))
301
330
 
302
331
  with self.subTest(f"Assert object_type {object_type} is valid"):
303
332
  app_label, model_name = object_type.split(".")
304
333
  ContentType.objects.get(app_label=app_label, model=model_name)
305
334
 
335
+ list_url += "&exclude_m2m=true"
336
+ with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
337
+ response = self.client.get(list_url, **self.header)
338
+
339
+ self.assertHttpStatus(response, status.HTTP_200_OK)
340
+ self.assertIsInstance(response.data, dict)
341
+ self.assertIn("results", response.data)
342
+ self.assert_no_verboten_content(response)
343
+
344
+ if m2m_fields:
345
+ if self.model._meta.app_label in [
346
+ "circuits",
347
+ "cloud",
348
+ "dcim",
349
+ "extras",
350
+ "ipam",
351
+ "tenancy",
352
+ "users",
353
+ "virtualization",
354
+ "wireless",
355
+ ]:
356
+ self.assertLess(
357
+ len(cqc), base_num_queries, "Number of queries did not decrease with ?exclude_m2m=true"
358
+ )
359
+ else:
360
+ # Less strict check for non-core APIs
361
+ self.assertLessEqual(
362
+ len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true"
363
+ )
364
+ else:
365
+ # No M2M fields to exclude
366
+ self.assertLessEqual(len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true")
367
+
368
+ for response_data in response.data["results"]:
369
+ for field in m2m_fields:
370
+ self.assertNotIn(field, response_data)
371
+ # TODO: we should assert that all other fields are still present, but there's a few corner cases...
372
+
306
373
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
307
374
  def test_list_objects_depth_1(self):
308
375
  """
309
376
  GET a list of objects using the "?depth=1" parameter.
310
377
  """
311
378
  depth_fields = self.get_depth_fields()
379
+ m2m_fields = self.get_m2m_fields()
312
380
  self.add_permissions(f"{self.model._meta.app_label}.view_{self.model._meta.model_name}")
313
- url = f"{self._get_list_url()}?depth=1"
314
- response = self.client.get(url, **self.header)
381
+ list_url = f"{self._get_list_url()}?depth=1"
382
+ with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
383
+ response = self.client.get(list_url, **self.header)
384
+ base_num_queries = len(cqc)
315
385
 
316
386
  self.assertHttpStatus(response, status.HTTP_200_OK)
317
387
  self.assertIsInstance(response.data, dict)
@@ -320,16 +390,65 @@ class APIViewTestCases:
320
390
  self.assert_no_verboten_content(response)
321
391
 
322
392
  for response_data in response.data["results"]:
393
+ for field in m2m_fields:
394
+ self.assertIn(field, response_data)
395
+ self.assertIsInstance(response_data[field], list)
323
396
  for field in depth_fields:
324
397
  self.assertIn(field, response_data)
325
398
  if isinstance(response_data[field], list):
326
399
  for entry in response_data[field]:
327
400
  self.assertIsInstance(entry, dict)
328
- self.assertTrue(is_uuid(entry["id"]))
401
+ if entry["object_type"] in ["auth.group"]:
402
+ self.assertIsInstance(entry["id"], int)
403
+ else:
404
+ self.assertTrue(is_uuid(entry["id"]))
405
+ self.assertGreater(len(entry.keys()), 3, entry) # not just id/object_type/url!
329
406
  else:
330
407
  if response_data[field] is not None:
331
408
  self.assertIsInstance(response_data[field], dict)
332
- self.assertTrue(is_uuid(response_data[field]["id"]))
409
+ if response_data[field]["object_type"] in ["auth.group"]:
410
+ self.assertIsInstance(response_data[field]["id"], int)
411
+ else:
412
+ self.assertTrue(is_uuid(response_data[field]["id"]))
413
+ self.assertGreater(len(response_data[field].keys()), 3, response_data[field])
414
+
415
+ list_url += "&exclude_m2m=true"
416
+ with CaptureQueriesContext(connections[DEFAULT_DB_ALIAS]) as cqc:
417
+ response = self.client.get(list_url, **self.header)
418
+
419
+ self.assertHttpStatus(response, status.HTTP_200_OK)
420
+ self.assertIsInstance(response.data, dict)
421
+ self.assertIn("results", response.data)
422
+ self.assert_no_verboten_content(response)
423
+
424
+ if m2m_fields:
425
+ if self.model._meta.app_label in [
426
+ "circuits",
427
+ "cloud",
428
+ "dcim",
429
+ "extras",
430
+ "ipam",
431
+ "tenancy",
432
+ "users",
433
+ "virtualization",
434
+ "wireless",
435
+ ]:
436
+ self.assertLess(
437
+ len(cqc), base_num_queries, "Number of queries did not decrease with ?exclude_m2m=true"
438
+ )
439
+ else:
440
+ # Less strict check for non-core APIs
441
+ self.assertLessEqual(
442
+ len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true"
443
+ )
444
+ else:
445
+ # No M2M fields to exclude
446
+ self.assertLessEqual(len(cqc), base_num_queries, "Number of queries increased with ?exclude_m2m=true")
447
+
448
+ for response_data in response.data["results"]:
449
+ for field in m2m_fields:
450
+ self.assertNotIn(field, response_data)
451
+ # TODO: we should assert that all other fields are still present, but there's a few corner cases...
333
452
 
334
453
  @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
335
454
  def test_list_objects_without_permission(self):
@@ -655,7 +774,11 @@ class APIViewTestCases:
655
774
  self.assertHttpStatus(response, status.HTTP_201_CREATED, csv_data)
656
775
  # Note that create via CSV is always treated as a bulk-create, and so the response is always a list of dicts
657
776
  new_instance = self._get_queryset().get(pk=response.data[0]["id"])
658
- self.assertNotEqual(new_instance.pk, orig_pk)
777
+ if isinstance(orig_pk, int):
778
+ self.assertNotEqual(new_instance.pk, orig_pk)
779
+ else:
780
+ # for our non-integer PKs, we're expecting the creation to respect the requested PK
781
+ self.assertEqual(new_instance.pk, orig_pk)
659
782
 
660
783
  new_serializer = serializer_class(new_instance, context={"request": None})
661
784
  new_data = new_serializer.data
@@ -737,7 +860,7 @@ class APIViewTestCases:
737
860
 
738
861
  def strip_serialized_object(this_object):
739
862
  """
740
- Only here to work around acceptable differences in PATCH response vs GET response which are known bugs.
863
+ Work around acceptable differences in PATCH response vs GET response which are known behaviors.
741
864
  """
742
865
  # Work around for https://github.com/nautobot/nautobot/issues/3321
743
866
  this_object.pop("last_updated", None)
@@ -746,6 +869,12 @@ class APIViewTestCases:
746
869
  this_object.pop("config_context", None)
747
870
  this_object.pop("relationships", None)
748
871
 
872
+ serializer = get_serializer_for_model(self.model)()
873
+ for field_name, field_instance in serializer.fields.items():
874
+ if field_instance.read_only:
875
+ # Likely a derived field, might change as a consequence of other data updates
876
+ this_object.pop(field_name, None)
877
+
749
878
  for value in this_object.values():
750
879
  if isinstance(value, dict):
751
880
  strip_serialized_object(value)
@@ -789,7 +918,7 @@ class APIViewTestCases:
789
918
  self.assertEqual(initial_serialized_object, serialized_object)
790
919
 
791
920
  # Verify ObjectChange creation -- yes, even though nothing actually changed
792
- # This may change (hah) at some point -- see https://github.com/nautobot/nautobot/issues/3321
921
+ # TODO: This may change (hah) at some point -- see https://github.com/nautobot/nautobot/issues/3321
793
922
  if hasattr(self.model, "to_objectchange"):
794
923
  objectchanges = lookup.get_changes_for_model(instance)
795
924
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
@@ -798,10 +927,16 @@ class APIViewTestCases:
798
927
  # Verify that a PATCH with some data updates that data correctly.
799
928
  response = self.client.patch(url, update_data, format="json", **self.header)
800
929
  self.assertHttpStatus(response, status.HTTP_200_OK)
930
+ serialized_object = response.json()
931
+ strip_serialized_object(serialized_object)
801
932
  # Check for unexpected side effects on fields we DIDN'T intend to update
802
933
  for field in initial_serialized_object:
803
934
  if field not in update_data:
804
- self.assertEqual(initial_serialized_object[field], serialized_object[field])
935
+ self.assertEqual(
936
+ initial_serialized_object[field],
937
+ serialized_object[field],
938
+ f"data changed unexpectedly for field '{field}'",
939
+ )
805
940
  instance.refresh_from_db()
806
941
  self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
807
942
 
@@ -810,6 +945,26 @@ class APIViewTestCases:
810
945
  objectchanges = lookup.get_changes_for_model(instance)
811
946
  self.assertEqual(objectchanges[0].action, extras_choices.ObjectChangeActionChoices.ACTION_UPDATE)
812
947
 
948
+ # Verify that a PATCH with ?exclude_m2m=true correctly excludes many-to-many fields from the response
949
+ # This also doubles as a test for idempotence of the PATCH request.
950
+ response = self.client.patch(url + "?exclude_m2m=true", update_data, format="json", **self.header)
951
+ self.assertHttpStatus(response, status.HTTP_200_OK)
952
+ m2m_fields = self.get_m2m_fields()
953
+ serialized_object = response.json()
954
+ strip_serialized_object(serialized_object)
955
+ for field in m2m_fields:
956
+ self.assertNotIn(field, serialized_object)
957
+ # Check for unexpected side effects on fields we DIDN'T intend to update
958
+ for field in initial_serialized_object:
959
+ if field not in update_data and field not in m2m_fields:
960
+ self.assertEqual(
961
+ initial_serialized_object[field],
962
+ serialized_object[field],
963
+ f"data changed unexpectedly for field '{field}'",
964
+ )
965
+ instance.refresh_from_db()
966
+ self.assertInstanceEqual(instance, update_data, exclude=self.validation_excluded_fields, api=True)
967
+
813
968
  def test_get_put_round_trip(self):
814
969
  """GET and then PUT an object and verify that it's accepted and unchanged."""
815
970
  self.maxDiff = None
@@ -1,6 +1,6 @@
1
1
  import random
2
2
  import string
3
- from typing import ClassVar, Iterable
3
+ from typing import ClassVar, Iterable, Optional
4
4
 
5
5
  from django.contrib.contenttypes.models import ContentType
6
6
  from django.db.models import Count, Q, QuerySet
@@ -30,6 +30,8 @@ class FilterTestCases:
30
30
  class BaseFilterTestCase(views.TestCase):
31
31
  """Base class for testing of FilterSets."""
32
32
 
33
+ queryset: ClassVar[Optional[QuerySet]] = None # TODO: declared as Optional only to avoid a breaking change
34
+
33
35
  def get_filterset_test_values(self, field_name, queryset=None):
34
36
  """Returns a list of distinct values from the requested queryset field to use in filterset tests.
35
37
 
@@ -71,8 +73,7 @@ class FilterTestCases:
71
73
  class FilterTestCase(BaseFilterTestCase):
72
74
  """Add common tests for all FilterSets."""
73
75
 
74
- queryset: ClassVar[QuerySet]
75
- filterset: ClassVar[FilterSet]
76
+ filterset: ClassVar[Optional[type[FilterSet]]] = None # TODO: declared Optional only to avoid breaking change
76
77
 
77
78
  # filter predicate fields that should be excluded from q test case
78
79
  exclude_q_filter_predicates = []
@@ -84,7 +85,7 @@ class FilterTestCases:
84
85
  # ["filter1"],
85
86
  # ["filter2", "field2__name"],
86
87
  # ]
87
- generic_filter_tests: ClassVar[Iterable]
88
+ generic_filter_tests: ClassVar[Iterable] = ()
88
89
 
89
90
  def setUp(self):
90
91
  for attr in ["queryset", "filterset", "generic_filter_tests"]:
@@ -94,12 +95,38 @@ class FilterTestCases:
94
95
 
95
96
  def get_q_filter(self):
96
97
  """Helper method to return q filter."""
98
+ self.assertIsNotNone(self.filterset)
97
99
  return self.filterset.declared_filters["q"].filter_predicates
98
100
 
101
+ def test_id(self):
102
+ """Verify that the filterset supports filtering by id with only lookup `__n`."""
103
+ self.assertIsNotNone(self.filterset)
104
+
105
+ with self.subTest("Assert `id`"):
106
+ params = {"id": list(self.queryset.values_list("pk", flat=True)[:2])}
107
+ expected_queryset = self.queryset.filter(id__in=params["id"])
108
+ filterset = self.filterset(params, self.queryset) # pylint: disable=not-callable # see assertion above
109
+ self.assertTrue(filterset.is_valid())
110
+ self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
111
+
112
+ with self.subTest("Assert negate lookup"):
113
+ params = {"id__n": list(self.queryset.values_list("pk", flat=True)[:2])}
114
+ expected_queryset = self.queryset.exclude(id__in=params["id__n"])
115
+ filterset = self.filterset(params, self.queryset) # pylint: disable=not-callable # see assertion above
116
+ self.assertTrue(filterset.is_valid())
117
+ self.assertQuerysetEqualAndNotEmpty(filterset.qs.order_by("id"), expected_queryset.order_by("id"))
118
+
119
+ with self.subTest("Assert invalid lookup"):
120
+ params = {"id__in": list(self.queryset.values_list("pk", flat=True)[:2])}
121
+ filterset = self.filterset(params, self.queryset) # pylint: disable=not-callable # see assertion above
122
+ self.assertFalse(filterset.is_valid())
123
+ self.assertIn("Unknown filter field", filterset.errors.as_text())
124
+
99
125
  def test_invalid_filter(self):
100
126
  """Verify that the filterset reports as invalid when initialized with an unsupported filter parameter."""
101
127
  params = {"ice_cream_flavor": ["chocolate"]}
102
- self.assertFalse(self.filterset(params, self.queryset).is_valid())
128
+ self.assertIsNotNone(self.filterset)
129
+ self.assertFalse(self.filterset(params, self.queryset).is_valid()) # pylint: disable=not-callable
103
130
 
104
131
  def test_filters_generic(self):
105
132
  """Test all multiple choice filters declared in `self.generic_filter_tests`.
@@ -167,13 +194,16 @@ class FilterTestCases:
167
194
  status=Status.objects.get_for_model(ContactAssociation).last(),
168
195
  )
169
196
 
197
+ if self.generic_filter_tests:
198
+ self.assertIsNotNone(self.filterset)
199
+
170
200
  for test in self.generic_filter_tests:
171
201
  filter_name = test[0]
172
202
  field_name = test[-1] # default to filter_name if a second list item was not supplied
173
203
  with self.subTest(f"{self.filterset.__name__} filter {filter_name} ({field_name})"):
174
204
  test_data = self.get_filterset_test_values(field_name)
175
205
  params = {filter_name: test_data}
176
- filterset_result = self.filterset(params, self.queryset).qs
206
+ filterset_result = self.filterset(params, self.queryset).qs # pylint: disable=not-callable
177
207
  qs_result = self.queryset.filter(**{f"{field_name}__in": test_data}).distinct()
178
208
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result, ordered=False)
179
209
 
@@ -184,6 +214,7 @@ class FilterTestCases:
184
214
  This test asserts that `filter=True` matches `self.queryset.filter(field__isnull=False)` and
185
215
  that `filter=False` matches `self.queryset.filter(field__isnull=True)`.
186
216
  """
217
+ self.assertIsNotNone(self.filterset)
187
218
  for filter_name, filter_object in self.filterset.get_filters().items():
188
219
  if not isinstance(filter_object, RelatedMembershipBooleanFilter):
189
220
  continue
@@ -191,11 +222,11 @@ class FilterTestCases:
191
222
  continue
192
223
  field_name = filter_object.field_name
193
224
  with self.subTest(f"{self.filterset.__name__} RelatedMembershipBooleanFilter {filter_name} (True)"):
194
- filterset_result = self.filterset({filter_name: True}, self.queryset).qs
225
+ filterset_result = self.filterset({filter_name: True}, self.queryset).qs # pylint: disable=not-callable
195
226
  qs_result = self.queryset.filter(**{f"{field_name}__isnull": filter_object.exclude}).distinct()
196
227
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
197
228
  with self.subTest(f"{self.filterset.__name__} RelatedMembershipBooleanFilter {filter_name} (False)"):
198
- filterset_result = self.filterset({filter_name: False}, self.queryset).qs
229
+ filterset_result = self.filterset({filter_name: False}, self.queryset).qs # pylint: disable=not-callable
199
230
  qs_result = self.queryset.exclude(**{f"{field_name}__isnull": filter_object.exclude}).distinct()
200
231
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
201
232
 
@@ -204,6 +235,8 @@ class FilterTestCases:
204
235
  if not issubclass(self.queryset.model, PrimaryModel):
205
236
  self.skipTest("Not a PrimaryModel")
206
237
 
238
+ self.assertIsNotNone(self.filterset)
239
+
207
240
  # Find an instance with at least two tags (should be common given our factory design)
208
241
  for instance in list(self.queryset):
209
242
  if len(instance.tags.all()) >= 2:
@@ -220,7 +253,7 @@ class FilterTestCases:
220
253
  self.queryset.first().tags.add(test_tags_filter_a, test_tags_filter_b)
221
254
  tags = [test_tags_filter_a, test_tags_filter_b]
222
255
  params = {"tags": [tags[0].name, tags[1].pk]}
223
- filterset_result = self.filterset(params, self.queryset).qs
256
+ filterset_result = self.filterset(params, self.queryset).qs # pylint: disable=not-callable
224
257
  # Tags is an AND filter not an OR filter
225
258
  qs_result = self.queryset.filter(tags=tags[0]).filter(tags=tags[1]).distinct()
226
259
  self.assertQuerysetEqualAndNotEmpty(filterset_result, qs_result)
@@ -271,6 +304,8 @@ class FilterTestCases:
271
304
  """
272
305
  self._assert_valid_filter_predicates(obj, obj_field_name)
273
306
 
307
+ self.assertIsNotNone(self.filterset)
308
+
274
309
  # Generic test only supports CharField or TextFields, skip all other types
275
310
  obj_field = obj._meta.get_field(obj_field_name)
276
311
  if not isinstance(obj_field, (CharField, TextField)):
@@ -290,7 +325,7 @@ class FilterTestCases:
290
325
  lookup = randomized_attr_value[1:].upper()
291
326
  model_queryset = self.queryset.filter(**{f"{filter_field_name}__icontains": lookup})
292
327
  params = {"q": lookup}
293
- filterset_result = self.filterset(params, self.queryset)
328
+ filterset_result = self.filterset(params, self.queryset) # pylint: disable=not-callable
294
329
 
295
330
  self.assertTrue(filterset_result.is_valid())
296
331
  self.assertQuerysetEqualAndNotEmpty(
@@ -1,5 +1,6 @@
1
1
  from django.test import tag, TestCase
2
2
 
3
+ from nautobot.core.api.constants import NON_FILTER_QUERY_PARAMS
3
4
  from nautobot.core.forms.fields import DynamicModelChoiceMixin
4
5
  from nautobot.core.utils.lookup import get_filterset_for_model
5
6
 
@@ -18,6 +19,7 @@ class FormTestCases:
18
19
  continue
19
20
  with self.subTest(f"Assert {self.form_class.__name__}.{field_name} query_params are valid."):
20
21
  query_params_fields = set(field_class.query_params.keys())
22
+ query_params_fields = query_params_fields - set(NON_FILTER_QUERY_PARAMS)
21
23
  if not query_params_fields:
22
24
  self.skipTest(f"{self.form_class.__name__}.{field_name} has no query_params")
23
25
  field_model = field_class.queryset.model