LTI provider -> editor connection error

78 views
Skip to first unread message

Tom Salyers

unread,
Feb 24, 2021, 11:59:03 AM2/24/21
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/base_events.py", 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

unread,
Mar 3, 2021, 10:56:12 AM3/3/21
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 settings.py. 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/forms.py) :

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

unread,
Mar 3, 2021, 10:58:27 AM3/3/21
to numbas...@googlegroups.com
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 numbas-users...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/numbas-users/5156f574-846b-4fe2-8d4a-bca8455f73ebn%40googlegroups.com.

Tom Salyers

unread,
Mar 3, 2021, 11:04:48 AM3/3/21
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

unread,
Mar 3, 2021, 11:07:47 AM3/3/21
to numbas...@googlegroups.com
Can you run `python manage.py 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 christia...@ncl.ac.uk if so.

Tom Salyers

unread,
Mar 3, 2021, 11:17:08 AM3/3/21
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/urls.py", 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

unread,
Mar 3, 2021, 11:19:01 AM3/3/21
to numbas...@googlegroups.com
Ah! That's what I thought: you need to install the django rest framework:

pip install djangorestframework

Does that fix it?

Tom Salyers

unread,
Mar 3, 2021, 11:24:26 AM3/3/21
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

unread,
Mar 3, 2021, 11:26:39 AM3/3/21
to numbas...@googlegroups.com
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

unread,
Mar 3, 2021, 11:29:11 AM3/3/21
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
Forward
0 new messages