Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed annotation object in a Canvas page #150

Merged
merged 6 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ In edX, the opaque `user_id` is unique within the scope of a course. In Canvas,
**The meaning of the ORG variable:**

Older versions of the tool used a global `ORG` variable to encode some differences, but this has been deprecated and should not be used.

6 changes: 3 additions & 3 deletions coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion hx_lti_assignment/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def get_target_external_options_list(self):
"""
Returns a list of options that are saved to the target_external_options model attribute
in CSV format.

Notes:
- since the model attribute could be null in the database, we have to
check if it's None before trying parse it.
Expand Down Expand Up @@ -323,3 +323,6 @@ def getColorValues(color):
except:
concat_tag_name += res[0] + " "
return result

def get_target_objects(self):
return self.assignment_objects.all()
86 changes: 85 additions & 1 deletion hx_lti_initializer/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django import forms
from django.db.models import Q
from hx_lti_assignment.models import Assignment
from oauthlib.common import Request as OAuthRequest
from oauthlib.oauth1 import Client as OAuthClient

from hx_lti_initializer.models import LTICourse, LTIProfile


Expand All @@ -21,3 +23,85 @@ def get_course_admins(self):
class Meta:
model = LTICourse
fields = ("course_name", "course_admins", "course_external_css_default")


class EmbedLtiSelectionForm(forms.Form):
'''
This form is intended to be used as part of the LTI content item embed workflow
to present a selection interface to the user.

Note: Attempted to use django-crispy-forms for layout, but the crispy template
for RadioSelect does not render subgroups of choices.
'''

def __init__(self, *args, **kwargs):
course_instance = kwargs.pop('course_instance')
content_item_return_url = kwargs.pop('content_item_return_url')
super().__init__(*args, **kwargs)

self.fields['content_item_return_url'] = forms.CharField(
widget=forms.HiddenInput(),
initial=content_item_return_url,
)

choices = self._get_choices(course_instance)
self.fields['content_item'] = forms.ChoiceField(
widget=forms.RadioSelect(attrs={'class':'content_item'}),
choices=choices,
initial=self._get_initial_choice(choices),
required=True,
)

def _get_choices(self, course_instance):
choices = []
for assignment in course_instance.get_published_assignments():
group_name = assignment.assignment_name
group_choices = []
for target in assignment.assignment_objects.all():
value = f"{assignment.assignment_id}/{target.pk}"
label = target.target_title
group_choices.append((value, label))
choices.append((group_name, group_choices))
return choices

def _get_initial_choice(self, choices):
if len(choices) == 0:
return None
group_name, group_choices = choices[0]
return group_choices[0][0]


class EmbedLtiResponseForm(forms.Form):
'''
This form is intended to be used as part of the LTI content item embed workflow
to return a response back to the tool consumer. Mostly it encapsulates the oauth
signing process.
'''

lti_message_type = forms.CharField(widget=forms.HiddenInput(), initial="ContentItemSelection")
lti_version = forms.CharField(widget=forms.HiddenInput(), initial="LTI-1p0")
content_items = forms.CharField(widget=forms.HiddenInput())
oauth_version = forms.CharField(widget=forms.HiddenInput())
oauth_nonce = forms.CharField(widget=forms.HiddenInput())
oauth_timestamp = forms.CharField(widget=forms.HiddenInput())
oauth_consumer_key = forms.CharField(widget=forms.HiddenInput())
oauth_callback = forms.CharField(widget=forms.HiddenInput())
oauth_signature_method = forms.CharField(widget=forms.HiddenInput())
oauth_signature = forms.CharField(widget=forms.HiddenInput())

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.data['lti_message_type'] = self.fields['lti_message_type'].initial
self.data['lti_version'] = self.fields['lti_version'].initial

def set_oauth_signature(self, url=None, consumer_key=None, consumer_secret=None):
body = dict(self.data) # shallow copy
headers = {}
request = OAuthRequest(url, "POST", body, headers)

# generate the oauth params and update the form data
client = OAuthClient(consumer_key, client_secret=consumer_secret, callback_uri='about:blank')
request.oauth_params = client.get_oauth_params(request)
oauth_signature = client.get_oauth_signature(request)
request.oauth_params.append(('oauth_signature', oauth_signature))
self.data.update(request.oauth_params)
8 changes: 7 additions & 1 deletion hx_lti_initializer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,16 @@ def add_user(self, lti_profile):

def get_assignments(self):
"""
Returns a QuerySet of assignments for this course, ordered by name.
Returns a QuerySet of all assignments (published and unpublished) for this course, ordered by name.
"""
return self.assignments.order_by(Lower("assignment_name"))

def get_published_assignments(self):
"""
Returns a QuerySet of published assignments for this course.
"""
return self.get_assignments().filter(is_published=True)


class LTICourseAdmin(models.Model):

Expand Down
Binary file added hx_lti_initializer/static/img/HxAT-128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hx_lti_initializer/static/img/HxAT-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hx_lti_initializer/static/img/HxAT-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added hx_lti_initializer/static/img/embed_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}HarvardX Annotation Tool{% endblock %}</title>
</head>
<body>
<form id="content_item_return" action="{{ content_item_return_url }}" method="post" encType="application/x-www-form-urlencoded">
{{ embed_lti_response_form }}
</form>
<script>
var content_item_return_form = document.forms["content_item_return"];
console.log("HxAT submitting ContentItemSelection response...");
content_item_return_form.submit();
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% load staticfiles %}
{% load bootstrap3 %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% block title %}HarvardX Annotation Tool{% endblock %}</title>
{% bootstrap_css %}
<style type="text/css">
ul.content_item, ul.content_item ul { list-style-type: none; }
</style>
</head>
<body>
<main class="container" style="margin: 20px;">
{% if selection_form.fields.content_item.choices|length > 0 %}
<form id="lti_selection" action="{% url 'hx_lti_initializer:embed_lti_response' %}" method="post">
{% csrf_token %}
{{ selection_form }}
<input type="submit" class="btn btn-primary" value="Insert this annotation content">
</form>
{% else %}
<p>No annotation content items available to add.</p>
{% endif %}
</main>
</body>
</html>
4 changes: 4 additions & 0 deletions hx_lti_initializer/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@
instructor_dashboard_student_list_view,
instructor_dashboard_view,
launch_lti,
embed_lti,
embed_lti_response,
)

urlpatterns = [
path("course/<int:id>/edit/", edit_course, name="edit_course",),
path("launch_lti/", launch_lti, name="launch_lti",),
path("embed_lti/", embed_lti, name="embed_lti",),
path("embed_lti_response/", embed_lti_response, name="embed_lti_response",),
path("admin_hub/", course_admin_hub, name="course_admin_hub",),
path(
"admin_hub/<path:course_id>/<slug:assignment_id>/<int:object_id>/preview/",
Expand Down
Loading