Forked from - Wed Jun 21 09:01:59 EDT 2017 https://github.com/tophatmonocle/ims_lti_py
Interestingly, the code in Pypi fails: https://pypi.python.org/pypi/ims_lti_py/0.5
And the code in the Harvard fork fails: https://github.com/harvard-dce/dce_lti_py
But the trunk of this works https://github.com/tophatmonocle/ims_lti_py
from collections import defaultdict
from lxml import etree, objectify
import oauth2
import six
from outcome_response import OutcomeResponse
from pytsugi_utils import InvalidLTIConfigError
REPLACE_REQUEST = "replaceResult"
DELETE_REQUEST = "deleteResult"
READ_REQUEST = "readResult"
accessors = [
"operation",
"score",
"result_data",
"outcome_response",
"message_identifier",
"lis_outcome_service_url",
"lis_result_sourcedid",
"consumer_key",
"consumer_secret",
"post_request",
]
class OutcomeRequest:
Class for consuming & generating LTI Outcome Requests.
- Outcome Request documentation:
http://www.imsglobal.org/LTI/v1p1/ltiIMGv1p1.html#_Toc319560472
This class can be used both by Tool Providers and Tool Consumers, though they each use it differently. The TP will use it to POST an OAuth-signed request to the TC. A TC will use it to parse such a request from a TP.
Initialize all our accessors to None
Store specified options in our accessors
Convenience method for creating a new OutcomeRequest from a request object.
post_request is assumed to be a Django HttpRequest object
POSTs the given score to the Tool Consumer with a replaceResult.
- OPTIONAL:
result_data must be a dictionary Note: ONLY ONE of these values can be in the dict at a time, due to the Canvas specification.
‘text’ : str text ‘url’ : str url
self.operation = REPLACE_REQUEST
self.score = score
self.result_data = result_data
if result_data is not None:
if len(result_data) > 1:
error_msg = (
"Dictionary result_data can only have one entry. "
"{0} entries were found.".format(len(result_data))
)
raise InvalidLTIConfigError(error_msg)
elif "text" not in result_data and "url" not in result_data:
error_msg = (
"Dictionary result_data can only have the key "
'"text" or the key "url".'
)
raise InvalidLTIConfigError(error_msg)
else:
return self.post_outcome_request()
else:
return self.post_outcome_request()
def post_delete_result(self):
POSTs a deleteRequest to the Tool Consumer.
POSTS a readResult to the Tool Consumer.
Check whether this request is a replaceResult request.
Check whether this request is a deleteResult request.
Check whether this request is a readResult request.
POST an OAuth signed request to the Tool Consumer.
monkey_patch_headers ensures that Authorization header is NOT lower cased
print(“My Normalize”, headers)
print(“My Normalize”, ret)
return ret
http._normalize_headers = my_normalize
monkey_patch_function = normalize
response, content = client.request(
self.lis_outcome_service_url,
"POST",
body=self.generate_request_xml(),
headers={"Content-Type": "application/xml"},
)
if monkey_patch_headers and monkey_patch_function:
import httplib2
http = httplib2.Http
http._normalize_headers = monkey_patch_function
self.outcome_response = OutcomeResponse.from_post_response(response, content)
return self.outcome_response
def process_xml(self, xml):
Parse Outcome Request data from XML.
Get result sourced id from resultRecord
Get result sourced id from resultRecord
Get result sourced id from resultRecord
self.lis_result_sourcedid = result.resultRecord.sourcedGUID.sourcedId
except:
pass
def has_required_attributes(self):
return (
self.consumer_key is not None
and self.consumer_secret is not None
and self.lis_outcome_service_url is not None
and self.lis_result_sourcedid is not None
and self.operation is not None
)
def generate_request_xml(self):
root = etree.Element(
"imsx_POXEnvelopeRequest",
xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0",
)
header = etree.SubElement(root, "imsx_POXHeader")
header_info = etree.SubElement(header, "imsx_POXRequestHeaderInfo")
version = etree.SubElement(header_info, "imsx_version")
version.text = "V1.0"
message_identifier = etree.SubElement(header_info, "imsx_messageIdentifier")
message_identifier.text = self.message_identifier
body = etree.SubElement(root, "imsx_POXBody")
request = etree.SubElement(body, "%s%s" % (self.operation, "Request"))
record = etree.SubElement(request, "resultRecord")
guid = etree.SubElement(record, "sourcedGUID")
sourcedid = etree.SubElement(guid, "sourcedId")
sourcedid.text = self.lis_result_sourcedid
if self.score is not None:
result = etree.SubElement(record, "result")
result_score = etree.SubElement(result, "resultScore")
language = etree.SubElement(result_score, "language")
language.text = "en"
text_string = etree.SubElement(result_score, "textString")
text_string.text = self.score.__str__()
if self.result_data:
resultData = etree.SubElement(result, "resultData")
if "text" in self.result_data:
resultDataText = etree.SubElement(resultData, "text")
resultDataText.text = self.result_data["text"]
elif "url" in self.result_data:
resultDataURL = etree.SubElement(resultData, "url")
resultDataURL.text = self.result_data["url"]
return etree.tostring(root, xml_declaration=True, encoding="utf-8")