LTI provider -> editor connection error

Skip to first unread message

Tom Salyers

Feb 24, 2021, 11:59:03 AMFeb 24
to Numbas Users
Hi, all.

I've got a working Numbas LTI provider connected to our Blackboard instance, and I'm now trying to connect a Numbas editor to it.

I've followed the installation instructions for the editor, and it looks like the application is up and running. When I attempt to connect to it from the LTI provider admin page, though, I get a bad request error:  "There was an error connecting to this URL: Request returned HTTP status code 400."

There's no indication of what's wrong in the main Apache error log, and the only thing I've found that looks remotely off is in /var/log/apache2/numbas_editor.error.log:

Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=False debug=False>>
Traceback (most recent call last):
  File "/usr/lib/python3.6/asyncio/", line 526, in __del__
NameError: name 'ResourceWarning' is not defined

Any idea of what might be going on? Did I miss a step somewhere? Thanks in advance.

Tom Salyers

Mar 3, 2021, 10:56:12 AMMar 3
to Numbas Users
Hi again, all.

I've made progress...after turning the debug setting on and investigating a bit more, I added our domain name to the editor's allowed host list in That seems to have cleared up the "400 Bad request" error, and I can go to the editor's URL in a web browser and get the expected login/sign up page.

However, I'm now getting a 404 error when trying to link the editor in the LTI provider. From what I can tell on the LTI provider side, it's trying to do a GET to /api/handshake on the editor (from the CreateEditorLinkForm class in /srv/numbas-lti-provider/numbas_lti/ :

response = requests.get('{}/api/handshake'.format(url), timeout=getattr(settings,'REQUEST_TIMEOUT',60))

....which doesn't seem to exist on the editor, even after adding 'editor_rest_api' to the installed apps in settings. I've updated the editor code to the latest from Github, and it looks like I've got the latest version of the LTI provider code, so Ithink I'm okay on that front. I'm not quite sure what bits I'm missing. Any ideas? Thanks (again) in advance.

Christian Lawson-Perfect

Mar 3, 2021, 10:58:27 AMMar 3
Hi Tom,
What does the /api/handshake URL look like when you load it in a browser? If you've got DEBUG = True in the editor's settings, it'll show you a list of available routes in the error message. That would help pin down what's going on.

You received this message because you are subscribed to the Google Groups "Numbas Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to
To view this discussion on the web, visit

Tom Salyers

Mar 3, 2021, 11:04:48 AMMar 3
to Numbas Users
Hi, Christian.

It's showing me a list of about 120 available routes, but /api/handshake isn't among them. (It doesn't look like anything under /api is, actually.) Here's the full list:

Using the URLconf defined in numbas.urls, Django tried these URL patterns, in this order:

  1. ^admin/
  2. logout/ [name='logout']
  3. login/ [name='login']
  4. logout/ [name='logout']
  5. password_change/ [name='password_change']
  6. password_change/done/ [name='password_change_done']
  7. password_reset/ [name='password_reset']
  8. password_reset/done/ [name='password_reset_done']
  9. reset/<uidb64>/<token>/ [name='password_reset_confirm']
  10. reset/done/ [name='password_reset_complete']
  11. accounts/
  12. ^$ [name='editor_index']
  13. ^stats/$ [name='global_stats']
  14. ^terms-of-use/$ [name='terms_of_use']
  15. ^privacy-policy/$ [name='privacy_policy']
  16. ^search/$ [name='search']
  17. ^top-search/$ [name='top-search']
  18. ^explore/$ [name='explore']
  19. ^projects/public$ [name='public_projects']
  20. ^project/new$ [name='project_new']
  21. ^project/(?P<pk>\d+)/$ [name='project_index']
  22. ^project/(?P<pk>\d+)/delete$ [name='project_delete']
  23. ^project/(?P<pk>\d+)/settings/options$ [name='project_settings_options']
  24. ^project/(?P<pk>\d+)/settings/members$ [name='project_settings_members']
  25. ^project/(?P<project_pk>\d+)/settings/add_member$ [name='project_settings_add_member']
  26. ^project/(?P<pk>\d+)/settings/transfer_ownership$ [name='project_transfer_ownership']
  27. ^project/(?P<pk>\d+)/watch/$ [name='project_watch']
  28. ^project/(?P<pk>\d+)/unwatch/$ [name='project_unwatch']
  29. ^project/(?P<pk>\d+)/leave/$ [name='project_leave']
  30. ^project/(?P<pk>\d+)/search/$ [name='project_search']
  31. ^project/(?P<pk>\d+)/browse/(?P<path>(.*/)*)?$ [name='project_browse']
  32. project/<int:project_pk>/new_folder [name='project_new_folder']
  33. ^project/(?P<pk>\d+)/comment$ [name='comment_on_project']
  34. folder/move [name='folder_move']
  35. folder/move_project [name='folder_move_project']
  36. folder/<int:pk>/rename [name='folder_rename']
  37. folder/<int:pk>/delete [name='folder_delete']
  38. ^item/(?P<pk>\d+)/preview/$ [name='item_preview']
  39. ^item/(?P<pk>\d+)/oembed/$ [name='item_oembed']
  40. ^item/(?P<pk>\d+)/publish$ [name='item_publish']
  41. ^item/(?P<pk>\d+)/unpublish$ [name='item_unpublish']
  42. ^item/(?P<pk>\d+)/set-access$ [name='set_access']
  43. ^item/(?P<pk>\d+)/move$ [name='item_move_project']
  44. ^item/(?P<pk>\d+)/transfer_ownership$ [name='item_transfer_ownership']
  45. ^items/compare/(?P<pk1>\d+)/(?P<pk2>\d+)$ [name='editoritem_compare']
  46. ^items/recently-published/feed$ [name='item_recently_published_feed']
  47. ^items/recently-published$ [name='item_recently_published']
  48. ^exam/new/$ [name='exam_new']
  49. ^exam/upload/$ [name='exam_upload']
  50. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/$ [name='exam_edit']
  51. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/copy/$ [name='exam_copy']
  52. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/delete/$ [name='exam_delete']
  53. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/preview/$ [name='exam_preview']
  54. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/embed/$ [name='exam_embed']
  55. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+).zip$ [name='exam_download']
  56. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+).exam$ [name='exam_source']
  57. ^exam/share/(?P<access>(view|edit))/(?P<share_uuid>.*)$ [name='share_exam']
  58. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/stamp$ [name='stamp_exam']
  59. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/comment$ [name='comment_on_exam']
  60. ^exam/(?P<pk>\d+)/(?P<slug>[\w-]+)/restore-point$ [name='set_restore_point_on_exam']
  61. ^exam/question-lists/(?P<pk>\d+)/$ [name='question_lists']
  62. ^question/new/$ [name='question_new']
  63. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/$ [name='question_edit']
  64. ^question/share/(?P<access>(view|edit))/(?P<share_uuid>.*)$ [name='share_question']
  65. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/comment$ [name='comment_on_question']
  66. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/stamp$ [name='stamp_question']
  67. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/restore-point$ [name='set_restore_point_on_question']
  68. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/resources/(?P<resource>.*)$ [name='view_resource']
  69. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/copy/$ [name='question_copy']
  70. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/delete/$ [name='question_delete']
  71. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/preview/$ [name='question_preview']
  72. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+)/embed/$ [name='question_embed']
  73. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+).zip$ [name='question_download']
  74. ^question/(?P<pk>\d+)/(?P<slug>[\w-]+).exam$ [name='question_source']
  75. ^resource/upload [name='upload_resource']
  76. ^timelineitem/(?P<pk>\d+)/hide$ [name='timelineitem_hide']
  77. ^timelineitem/(?P<pk>\d+)/unhide$ [name='timelineitem_unhide']
  78. ^timelineitem/(?P<pk>\d+)/delete$ [name='timelineitem_delete']
  79. ^stamp/(?P<pk>\d+)/delete$ [name='stamp_delete']
  80. ^restore_point/(?P<pk>\d+)/revert$ [name='restore_point_revert']
  81. ^theme/new/$ [name='theme_new']
  82. ^theme/upload/$ [name='theme_upload']
  83. ^themes/(?P<pk>\d+)/download$ [name='theme_download']
  84. ^themes/(?P<pk>\d+)/edit$ [name='theme_edit']
  85. ^themes/(?P<pk>\d+)/edit_source$ [name='theme_edit_source']
  86. ^themes/(?P<pk>\d+)/replace_file$ [name='theme_replace_file']
  87. ^themes/(?P<pk>\d+)/documentation$ [name='theme_documentation']
  88. ^themes/(?P<pk>\d+)/access$ [name='theme_access']
  89. ^themes/(?P<theme_pk>\d+)/access/add$ [name='theme_add_access']
  90. ^themes/(?P<pk>\d+)/delete$ [name='theme_delete']
  91. ^themes/(?P<pk>\d+)/delete_file$ [name='theme_delete_file']
  92. ^extension/new/$ [name='extension_new']
  93. ^extension/upload/$ [name='extension_upload']
  94. ^extensions/(?P<pk>\d+)/download$ [name='extension_download']
  95. ^extensions/(?P<pk>\d+)/edit$ [name='extension_edit']
  96. ^extensions/(?P<pk>\d+)/replace_file$ [name='extension_replace_file']
  97. ^extensions/(?P<pk>\d+)/edit_source$ [name='extension_edit_source']
  98. ^extensions/(?P<pk>\d+)/documentation$ [name='extension_documentation']
  99. ^extensions/(?P<pk>\d+)/access$ [name='extension_access']
  100. ^extensions/(?P<extension_pk>\d+)/access/add$ [name='extension_add_access']
  101. ^extensions/(?P<pk>\d+)/delete$ [name='extension_delete']
  102. ^extensions/(?P<pk>\d+)/delete_file$ [name='extension_delete_file']
  103. ^part_type/new/$ [name='custom_part_type_new']
  104. ^part_type/upload/$ [name='custom_part_type_upload']
  105. ^part_type/(?P<pk>\d+)/edit$ [name='custom_part_type_edit']
  106. ^part_type/(?P<pk>\d+)/copy$ [name='custom_part_type_copy']
  107. ^part_type/(?P<pk>\d+)/delete$ [name='custom_part_type_delete']
  108. ^part_type/(?P<pk>\d+)/publish$ [name='custom_part_type_publish']
  109. ^part_type/(?P<pk>\d+)/unpublish$ [name='custom_part_type_unpublish']
  110. ^part_type/(?P<pk>\d+)/source$ [name='custom_part_type_source']
  111. ^notification/(?P<pk>\d+)/open [name='open_notification']
  112. notifications/unread_json/ [name='unread']
  113. ^question_basket/$ [name='basket']
  114. ^question_basket/add/$ [name='add_question_to_basket']
  115. ^question_basket/remove/$ [name='remove_question_from_basket']
  116. ^question_basket/create_exam/$ [name='create_exam_from_basket']
  117. ^question_basket/empty/$ [name='empty_question_basket']
  118. ^pullrequest/create$ [name='pullrequest_new']
  119. ^pullrequest/(?P<pk>\d+)/close$ [name='pullrequest_close']
  120. ^migrate/
  121. ^notifications/
  122. ^media\/(?P<path>.*)$

The current path, api/handshake, didn't match any of these.

Christian Lawson-Perfect

Mar 3, 2021, 11:07:47 AMMar 3
Can you run `python shell` in the editor's directory, and try:

        from editor_rest_api.urls import urls as rest_urls

Does that give an ImportError?
I'm free for a video call for the next half an hour, or any time tomorrow, if you'd like - email an invitation to if so.

Tom Salyers

Mar 3, 2021, 11:17:08 AMMar 3
to Numbas Users
It gives me a ModuleNotFoundError, actually:

>>> from editor_rest_api.urls import urls as rest_urls
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/srv/www/numbas_editor/editor_rest_api/", line 3, in <module>
    from rest_framework import routers
ModuleNotFoundError: No module named 'rest_framework'

I can't fit in a video call today, but I might be able to tomorrow between 11-12 or 2-4, if need be.

Christian Lawson-Perfect

Mar 3, 2021, 11:19:01 AMMar 3
Ah! That's what I thought: you need to install the django rest framework:

pip install djangorestframework

Does that fix it?

Tom Salyers

Mar 3, 2021, 11:24:26 AMMar 3
to Numbas Users

That looks like that took care of it once I turned debug back off--thanks! I've now got our LTI provider linked up with our editor.

Was the Django REST framework left out of requirements.txt somehow, or did it just not get installed for some reason?

Christian Lawson-Perfect

Mar 3, 2021, 11:26:39 AMMar 3
It's not in the standard requirements because I wasn't sure about having it available by default.
I don't think the current setup for linking the editor to the LTI tool is particularly useful, so I want to rework the API at some point in the future. Ideally, an instructor would be able to pick from their own content inside the LTI tool, rather than just published stuff.

Tom Salyers

Mar 3, 2021, 11:29:11 AMMar 3
to Numbas Users
Ah, fair enough. Thanks again--it's nice to work with an external tool that's in Python for a change. There's too much PHP around for my tastes. :)
Reply all
Reply to author
0 new messages