From 36930ee461c57c06c4da070de27e76ec3a8e9341 Mon Sep 17 00:00:00 2001 From: Shimul Chowdhury <shimul@opencraft.com> Date: Tue, 29 Dec 2020 19:33:21 +0600 Subject: [PATCH] Add support for image content type --- lti_consumer/lti_1p3/constants.py | 2 +- .../extensions/rest_framework/constants.py | 2 + .../extensions/rest_framework/serializers.py | 19 ++++ .../html/lti-dl/render_dl_content.html | 2 + .../templates/html/lti-dl/render_image.html | 14 +++ .../plugin/test_views_lti_deep_linking.py | 96 +++++++++++++++++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 lti_consumer/templates/html/lti-dl/render_image.html diff --git a/lti_consumer/lti_1p3/constants.py b/lti_consumer/lti_1p3/constants.py index b7a41de..ff0e28e 100644 --- a/lti_consumer/lti_1p3/constants.py +++ b/lti_consumer/lti_1p3/constants.py @@ -56,7 +56,7 @@ LTI_DEEP_LINKING_ACCEPTED_TYPES = [ 'ltiResourceLink', 'link', 'html', - # TODO: implement "image" support, + 'image', # TODO: implement "file" support, ] diff --git a/lti_consumer/lti_1p3/extensions/rest_framework/constants.py b/lti_consumer/lti_1p3/extensions/rest_framework/constants.py index b812d79..e7797cc 100644 --- a/lti_consumer/lti_1p3/extensions/rest_framework/constants.py +++ b/lti_consumer/lti_1p3/extensions/rest_framework/constants.py @@ -5,6 +5,7 @@ from .serializers import ( LtiDlLtiResourceLinkSerializer, LtiDlLinkSerializer, LtiDlHtmlSerializer, + LtiDlImageSerializer, ) @@ -12,4 +13,5 @@ LTI_DL_CONTENT_TYPE_SERIALIZER_MAP = { "ltiResourceLink": LtiDlLtiResourceLinkSerializer, "link": LtiDlLinkSerializer, "html": LtiDlHtmlSerializer, + "image": LtiDlImageSerializer, } diff --git a/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py b/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py index cc31a04..fb5d282 100644 --- a/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py +++ b/lti_consumer/lti_1p3/extensions/rest_framework/serializers.py @@ -345,3 +345,22 @@ class LtiDlHtmlSerializer(serializers.Serializer): html = serializers.CharField() title = serializers.CharField(max_length=255, required=False) text = serializers.CharField(required=False) + + +# pylint: disable=abstract-method +class LtiDlImageSerializer(serializers.Serializer): + """ + LTI Deep Linking - image Serializer. + + This serializer implements validation for the Image content type. + + Reference: + http://www.imsglobal.org/spec/lti-dl/v2p0#image + """ + url = serializers.URLField(max_length=500) + title = serializers.CharField(max_length=255, required=False) + text = serializers.CharField(required=False) + icon = LtiDLIconPropertySerializer(required=False) + thumbnail = LtiDLIconPropertySerializer(required=False) + width = serializers.IntegerField(min_value=1, required=False) + height = serializers.IntegerField(min_value=1, required=False) diff --git a/lti_consumer/templates/html/lti-dl/render_dl_content.html b/lti_consumer/templates/html/lti-dl/render_dl_content.html index 7d2b71b..7e00c3b 100644 --- a/lti_consumer/templates/html/lti-dl/render_dl_content.html +++ b/lti_consumer/templates/html/lti-dl/render_dl_content.html @@ -10,6 +10,8 @@ {% include "html/lti-dl/render_link.html" with item=item attrs=item.attributes %} {% elif item.content_type == 'html' %} {% include "html/lti-dl/render_html.html" with item=item attrs=item.attributes %} + {% elif item.content_type == 'image' %} + {% include "html/lti-dl/render_image.html" with item=item attrs=item.attributes %} {% endif %} {% endfor %} </body> diff --git a/lti_consumer/templates/html/lti-dl/render_image.html b/lti_consumer/templates/html/lti-dl/render_image.html new file mode 100644 index 0000000..8e66abf --- /dev/null +++ b/lti_consumer/templates/html/lti-dl/render_image.html @@ -0,0 +1,14 @@ +{% if attrs.title %}<h2>{{ attrs.title }}</h2>{% endif %} +{% if attrs.text %}<p>{{ attrs.text }}</p>{% endif %} + +{% if attrs.thumbnail %} +<a href="{{ attrs.url }}" {% if attrs.title %}alt="{{ attrs.title }}"{% endif %}> + <img src="{{ attrs.thumbnail.url }}" {% if attrs.title %}alt="{{ attrs.title }}"{% endif %} width="{{ attrs.thumbnail.width }}" height="{{ attrs.thumbnail.height }}" /> +</a> +{% elif attrs.icon %} +<a href="{{ attrs.url }}" {% if attrs.title %}alt="{{ attrs.title }}"{% endif %}> + <img src="{{ attrs.icon.url }}" {% if attrs.title %}alt="{{ attrs.title }}"{% endif %} width="{{ attrs.icon.width }}" height="{{ attrs.icon.height }}" /> +</a> +{% else %} +<img src="{{ attrs.url }}" {% if attrs.title %}alt="{{ attrs.title }}" {% endif %}{% if attrs.width %}width="{{ attrs.width }}" {% endif %}{% if attrs.height %}height="{{ attrs.height }}"{% endif %} /> +{% endif %} diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py index e095357..746626f 100644 --- a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py +++ b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py @@ -350,6 +350,46 @@ class LtiDeepLinkingResponseEndpointTestCase(LtiDeepLinkingTestCase): content_item, is_valid = test_data self._content_type_validation_test_helper(content_item, is_valid) + @ddt.data( + ({"type": "image"}, False), + ({"type": "image", "url": "http://ex.com/image"}, True), + ({ + "type": "image", + "url": "http://ex.com/image", + "text": "This is a link", + "title": "This is a link" + }, True), + + # invalid icon + ({"type": "image", "url": "https://example.com/image", "icon": {}}, False), + # valid icon + ({ + "type": "image", + "url": "https://example.com/image", + "icon": {"url": "https://ex.com/icon", "width": 20, "height": 20} + }, True), + + # invalid thumbnail + ({"type": "image", "url": "https://example.com/image", "thumbnail": {}}, False), + # valid thumbnail + ({ + "type": "image", + "url": "https://example.com/image", + "thumbnail": {"url": "https://ex.com/icon", "width": 20, "height": 20} + }, True), + ) + def test_image_content_type(self, test_data): + """ + Tests validation for `image` content type. + + Args: + self + test_data (tuple): 1st element is the datastructure to test, + and the second one indicates wether it's valid or not. + """ + content_item, is_valid = test_data + self._content_type_validation_test_helper(content_item, is_valid) + @ddt.ddt class LtiDeepLinkingContentEndpointTestCase(LtiDeepLinkingTestCase): @@ -508,3 +548,59 @@ class LtiDeepLinkingContentEndpointTestCase(LtiDeepLinkingTestCase): self.assertContains(resp, '<p>{}</p>'.format(test_data['text'])) self.assertContains(resp, test_data['html']) + + @ddt.data( + {'url': 'https://path.to.image'}, + {'url': 'https://path.to.image', 'title': 'With Title'}, + {'url': 'https://path.to.image', 'title': 'With Title', 'text': 'With Text'}, + { + 'url': 'https://path.to.image', 'title': 'With Title', 'text': 'With Text', + 'width': '400px', 'height': '200px', + }, + { + 'url': 'https://path.to.image', 'title': 'With Title', 'text': 'With Text', + 'icon': {'url': 'https://path.to.icon', 'width': '20px', 'height': '20px'}, + }, + { + 'url': 'https://path.to.image', 'title': 'With Title', 'text': 'With Text', + 'thumbnail': {'url': 'https://path.to.thumbnail', 'width': '20px', 'height': '20px'}, + }, + ) + @patch('lti_consumer.plugin.views.has_block_access', return_value=True) + def test_dl_content_type_image(self, test_data, has_block_access): # pylint: disable=unused-argument + """ + Test if image content type successfully rendered. + """ + attributes = {'type': LtiDlContentItem.IMAGE} + attributes.update(test_data) + + LtiDlContentItem.objects.create( + lti_configuration=self.lti_config, + content_type=LtiDlContentItem.IMAGE, + attributes=attributes + ) + + resp = self.client.get(self.url) + self.assertEqual(resp.status_code, 200) + + if test_data.get('title'): + self.assertContains(resp, '<h2>{}</h2>'.format(test_data['title'])) + self.assertContains(resp, 'alt="{}"'.format(test_data['title'])) + + if test_data.get('text'): + self.assertContains(resp, '<p>{}</p>'.format(test_data['text'])) + + if test_data.get('thumbnail'): + self.assertContains(resp, '<a href="{}"'.format(test_data['url'])) + self.assertContains(resp, '<img src="{}"'.format(test_data['thumbnail']['url'])) + elif test_data.get('icon'): + self.assertContains(resp, '<a href="{}"'.format(test_data['url'])) + self.assertContains(resp, '<img src="{}"'.format(test_data['icon']['url'])) + else: + self.assertContains(resp, '<img src="{}"'.format(test_data['url'])) + + if test_data.get('width'): + self.assertContains(resp, 'width="{}"'.format(test_data['width'])) + + if test_data.get('height'): + self.assertContains(resp, 'height="{}"'.format(test_data['height'])) -- GitLab