diff --git a/openedx_lti_tool_plugin/deep_linking/forms.py b/openedx_lti_tool_plugin/deep_linking/forms.py index d8a9b85..e1f8918 100644 --- a/openedx_lti_tool_plugin/deep_linking/forms.py +++ b/openedx_lti_tool_plugin/deep_linking/forms.py @@ -1,21 +1,10 @@ """Django Forms.""" -import json import logging -from importlib import import_module -from typing import List, Optional, Tuple from django import forms -from django.conf import settings -from django.http.request import HttpRequest -from django.urls import reverse -from django.utils.translation import gettext as _ -from jsonschema import validate from pylti1p3.deep_link_resource import DeepLinkResource -from openedx_lti_tool_plugin.apps import OpenEdxLtiToolPluginConfig as app_config -from openedx_lti_tool_plugin.edxapp_wrapper.site_configuration_module import configuration_helpers -from openedx_lti_tool_plugin.models import CourseContext -from openedx_lti_tool_plugin.utils import get_identity_claims +from openedx_lti_tool_plugin.validators import JSONSchemaValidator log = logging.getLogger(__name__) @@ -28,6 +17,7 @@ class DeepLinkingForm(forms.Form): 'items': { 'type': 'object', 'properties': { + 'type': {'type': 'string'}, 'url': {'type': 'string'}, 'title': {'type': 'string'}, }, @@ -35,161 +25,15 @@ class DeepLinkingForm(forms.Form): }, } - content_items = forms.MultipleChoiceField( + content_items = forms.JSONField( required=False, - widget=forms.CheckboxSelectMultiple, - label=_('Content Items'), + validators=[JSONSchemaValidator(CONTENT_ITEMS_SCHEMA)], ) - def __init__( - self, - *args: tuple, - request: HttpRequest, - launch_data: dict, - **kwargs: dict, - ): - """Class __init__ method. - - Initialize class instance attributes and set the choices - of the content_items field. - - Args: - *args: Variable length argument list. - request: HttpRequest object. - launch_data: Launch message data. - **kwargs: Arbitrary keyword arguments. - - """ - super().__init__(*args, **kwargs) - self.request = request - self.launch_data = launch_data - self.fields['content_items'].choices = self.get_content_items_choices() - - def get_content_items_choices(self) -> List[Optional[Tuple[str, str]]]: - """Get content_items field choices. - - This method will get the content_items field choices from a list - of content items dictionaries provided by the get_content_items method or - the get_content_items_from_provider method if a content items provider is setup. - - A content item is a JSON that represents a content the LTI Platform can consume, - this could be an LTI resource link launch URL, an URL to a resource hosted - on the internet, an HTML fragment, or any other kind of content type defined - by the `type` JSON attribute. - - Each choice that this method returns is a JSON string representing a content item. - - Returns: - A list of tuples with content_items field choices or empty list. - - .. _LTI Deep Linking Specification - Content Item Types: - https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types - - """ - return [ - (json.dumps(content_item), content_item.get('title', '')) - for content_item in ( - self.get_content_items_from_provider() - or self.get_content_items() - ) - ] - - def get_content_items_from_provider(self) -> List[Optional[dict]]: - """Get content items from a provider function. - - This method will try to obtain content items from a provider function. - To setup a provider function the OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER setting - must be set to a string with the full path to the function that will act has a provider: - - Example: - OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER = 'example.module.path.provider_function' - - This method will then try to import and call the function, the call will include - the HTTPRequest object and deep linking launch data dictionary received from - the deep linking request has arguments. - - The content items returned from the function must be a list of dictionaries, - this list will be validated with a JSON Schema validator using a schema defined - in the CONTENT_ITEMS_SCHEMA constant. - - Returns: - A list with content item dictionaries. - - An empty list if OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER setting is None. - or there was an Exception importing or calling the provider function, - or the data returned by the provider function is not valid. - or the provider function returned an empty list. - - .. _LTI Deep Linking Specification - Content Item Types: - https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types - - """ - if not (setting := configuration_helpers().get_value( - 'OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER', - settings.OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER, - )): - return [] - - try: - path, name = str(setting).rsplit('.', 1) - content_items = getattr(import_module(path), name)( - self.request, - self.launch_data, - ) - validate(content_items, self.CONTENT_ITEMS_SCHEMA) - - return content_items - - except Exception as exc: # pylint: disable=broad-exception-caught - log_extra = { - 'setting': setting, - 'exception': str(exc), - } - log.error(f'Error obtaining content items from provider: {log_extra}') - - return [] - - def get_content_items(self) -> List[Optional[dict]]: - """Get content items. - - Returns: - A list of content item dictionaries or an empty list. - - .. _LTI Deep Linking Specification - Content Item Types: - https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types - - """ - iss, aud, _sub, _pii = get_identity_claims(self.launch_data) - - return [ - { - 'url': self.build_content_item_url(course), - 'title': course.title, - } - for course in CourseContext.objects.all_for_lti_tool(iss, aud) - ] - - def build_content_item_url(self, course: CourseContext) -> str: - """Build content item URL. - - Args: - course: CourseContext object. - - Returns: - An absolute LTI 1.3 resource link launch URL. - - """ - return self.request.build_absolute_uri( - reverse( - f'{app_config.name}:1.3:resource-link:launch-course', - kwargs={'course_id': course.course_id}, - ) - ) - def clean(self) -> dict: """Form clean. - This method will transform all the JSON strings from the cleaned content_items data + This method will transform all the dictionaries from the cleaned content_items data into a list of DeepLinkResource objects that will be added to the cleaned data dictionary deep_link_resources key. @@ -204,8 +48,8 @@ def clean(self) -> dict: deep_link_resources = [] for content_item in self.cleaned_data.get('content_items', []): - content_item = json.loads(content_item) deep_link_resource = DeepLinkResource() + deep_link_resource.set_type(content_item.get('type')) deep_link_resource.set_title(content_item.get('title')) deep_link_resource.set_url(content_item.get('url')) deep_link_resources.append(deep_link_resource) diff --git a/openedx_lti_tool_plugin/deep_linking/tests/test_forms.py b/openedx_lti_tool_plugin/deep_linking/tests/test_forms.py index 506ae22..2a36684 100644 --- a/openedx_lti_tool_plugin/deep_linking/tests/test_forms.py +++ b/openedx_lti_tool_plugin/deep_linking/tests/test_forms.py @@ -1,38 +1,26 @@ """Tests forms module.""" from unittest.mock import MagicMock, patch -from django.conf import settings from django.test import TestCase -from testfixtures import log_capture -from testfixtures.logcapture import LogCaptureForDecorator -from openedx_lti_tool_plugin.apps import OpenEdxLtiToolPluginConfig as app_config from openedx_lti_tool_plugin.deep_linking.forms import DeepLinkingForm from openedx_lti_tool_plugin.deep_linking.tests import MODULE_PATH -from openedx_lti_tool_plugin.tests import AUD, ISS MODULE_PATH = f'{MODULE_PATH}.forms' -class DeepLinkingFormTestCase(TestCase): - """DeepLinkingForm TestCase.""" +class TestDeepLinkingForm(TestCase): + """Test DeepLinkingForm class.""" def setUp(self): """Set up test fixtures.""" super().setUp() self.form_class = DeepLinkingForm - self.request = MagicMock() - self.launch_data = {} - self.form_kwargs = {'request': self.request, 'launch_data': self.launch_data} - self.title = 'example-title' - self.url = 'http://example.com' - self.content_item = {'url': self.url, 'title': self.title} - self.content_item_json = f'{{"url": "{self.url}", "title": "{self.title}"}}' - self.course = MagicMock() - - -class TestDeepLinkingForm(DeepLinkingFormTestCase): - """Test DeepLinkingForm class.""" + self.content_item = { + 'type': 'test-type', + 'url': 'tset-title', + 'title': 'http://test.com', + } def test_class_attributes(self): """Test class attributes.""" @@ -43,6 +31,7 @@ def test_class_attributes(self): 'items': { 'type': 'object', 'properties': { + 'type': {'type': 'string'}, 'url': {'type': 'string'}, 'title': {'type': 'string'}, }, @@ -51,238 +40,26 @@ def test_class_attributes(self): }, ) - @patch.object(DeepLinkingForm, 'get_content_items_choices', return_value=[]) - def test_init( - self, - get_content_items_choices_mock: MagicMock, - ): - """Test __init__ method.""" - form = self.form_class(**self.form_kwargs) - - self.assertEqual(form.request, self.request) - self.assertEqual(form.launch_data, self.launch_data) - self.assertEqual( - list(form.fields['content_items'].choices), - get_content_items_choices_mock.return_value, - ) - get_content_items_choices_mock.assert_called_once_with() - - @patch(f'{MODULE_PATH}.get_identity_claims') - @patch(f'{MODULE_PATH}.CourseContext') - @patch.object(DeepLinkingForm, 'build_content_item_url') - @patch.object(DeepLinkingForm, 'get_content_items_choices') - def test_get_content_items( - self, - get_content_items_choices_mock: MagicMock, # pylint: disable=unused-argument - build_content_item_url_mock: MagicMock, - course_context_mock: MagicMock, - get_identity_claims_mock: MagicMock, - ): - """Test get_content_items method.""" - get_identity_claims_mock.return_value = ISS, AUD, None, None - course_context_mock.objects.all_for_lti_tool.return_value = [self.course] - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items(), - [ - { - 'url': build_content_item_url_mock.return_value, - 'title': self.course.title, - }, - ], - ) - get_identity_claims_mock.assert_called_once_with(self.launch_data) - course_context_mock.objects.all_for_lti_tool.assert_called_once_with(ISS, AUD) - build_content_item_url_mock.assert_called_once_with(self.course) - - @patch(f'{MODULE_PATH}.reverse') - @patch.object(DeepLinkingForm, 'get_content_items_choices') - def test_build_content_item_url( - self, - get_content_items_choices_mock: MagicMock, # pylint: disable=unused-argument - reverse_mock: MagicMock, - ): - """Test build_content_item_url method.""" - self.assertEqual( - self.form_class(**self.form_kwargs).build_content_item_url(self.course), - self.request.build_absolute_uri.return_value, - ) - reverse_mock.assert_called_once_with( - f'{app_config.name}:1.3:resource-link:launch-course', - kwargs={'course_id': self.course.course_id}, - ) - @patch(f'{MODULE_PATH}.super') - @patch(f'{MODULE_PATH}.json.loads') @patch(f'{MODULE_PATH}.DeepLinkResource') - @patch.object(DeepLinkingForm, '__init__', return_value=None) def test_clean( self, - init_mock: MagicMock, # pylint: disable=unused-argument deep_link_resource_mock: MagicMock, - json_loads_mock: MagicMock, super_mock: MagicMock, ): """Test clean method.""" - json_loads_mock.return_value = self.content_item - form = self.form_class(**self.form_kwargs) - form.cleaned_data = {'content_items': [self.content_item_json]} + form = self.form_class() + form.cleaned_data = {'content_items': [self.content_item]} self.assertEqual(form.clean(), form.cleaned_data) super_mock.assert_called_once_with() - json_loads_mock.assert_called_once_with(self.content_item_json) deep_link_resource_mock.assert_called_once_with() - deep_link_resource_mock().set_title.assert_called_once_with(self.title) - deep_link_resource_mock().set_url.assert_called_once_with(self.url) - - -@patch.object(DeepLinkingForm, 'get_content_items_from_provider') -@patch.object(DeepLinkingForm, 'get_content_items') -@patch(f'{MODULE_PATH}.json.dumps') -@patch.object(DeepLinkingForm, '__init__', return_value=None) -class TestDeepLinkingFormGetContentItemsChoices(DeepLinkingFormTestCase): - """Test DeepLinkingForm.get_content_items_choices method.""" - - def test_with_get_content_items_from_provider( - self, - init_mock: MagicMock, # pylint: disable=unused-argument - json_dumps_mock: MagicMock, - get_content_items_mock: MagicMock, - get_content_items_from_provider_mock: MagicMock, - ): - """Test with values from get_content_items_from_provider method.""" - get_content_items_from_provider_mock.return_value = [self.content_item] - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items_choices(), - [(json_dumps_mock.return_value, self.title)], - ) - json_dumps_mock.assert_called_once_with(self.content_item) - get_content_items_from_provider_mock.assert_called_once_with() - get_content_items_mock.assert_not_called() - - def test_with_get_content_items( - self, - init_mock: MagicMock, # pylint: disable=unused-argument - json_dumps_mock: MagicMock, - get_content_items_mock: MagicMock, - get_content_items_from_provider_mock: MagicMock, - ): - """Test with values from get_content_items method.""" - get_content_items_from_provider_mock.return_value = [] - get_content_items_mock.return_value = [self.content_item] - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items_choices(), - [(json_dumps_mock.return_value, self.title)], + deep_link_resource_mock().set_type.assert_called_once_with( + self.content_item['type'], ) - json_dumps_mock.assert_called_once_with(self.content_item) - get_content_items_from_provider_mock.assert_called_once_with() - get_content_items_mock.assert_called_once_with() - - -@patch(f'{MODULE_PATH}.configuration_helpers') -@patch(f'{MODULE_PATH}.import_module') -@patch(f'{MODULE_PATH}.getattr') -@patch(f'{MODULE_PATH}.validate') -@patch.object(DeepLinkingForm, 'get_content_items_choices') -class TestDeepLinkingFormGetContentItemsChoicesFromProvider(DeepLinkingFormTestCase): - """Test DeepLinkingForm.get_content_items_from_provider method.""" - - def setUp(self): - """Set up test fixtures.""" - super().setUp() - self.setting = settings.OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER - self.setting_name = 'OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER' - self.setting_module = 'example.module.path' - self.setting_function = 'example_function' - self.setting_value = f'{self.setting_module}.{self.setting_function}' - - def test_with_setting_value( - self, - get_content_items_choices_mock: MagicMock, # pylint: disable=unused-argument - validate_mock: MagicMock, - getattr_mock: MagicMock, - import_module_mock: MagicMock, - configuration_helpers_mock: MagicMock, - ): - """Test with setting value (happy path).""" - configuration_helpers_mock().get_value.return_value = self.setting_value - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items_from_provider(), - getattr_mock.return_value.return_value, + deep_link_resource_mock().set_title.assert_called_once_with( + self.content_item['title'], ) - configuration_helpers_mock().get_value.assert_called_once_with( - self.setting_name, - self.setting, - ) - import_module_mock.assert_called_once_with(self.setting_module) - getattr_mock.assert_called_once_with(import_module_mock(), self.setting_function) - getattr_mock().assert_called_once_with(self.request, self.launch_data) - validate_mock.assert_called_once_with( - getattr_mock()(), - self.form_class.CONTENT_ITEMS_SCHEMA, - ) - - def test_without_setting_value( - self, - get_content_items_choices_mock: MagicMock, # pylint: disable=unused-argument - validate_mock: MagicMock, - getattr_mock: MagicMock, - import_module_mock: MagicMock, - configuration_helpers_mock: MagicMock, - ): - """Test without setting value.""" - configuration_helpers_mock().get_value.return_value = '' - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items_from_provider(), - [], - ) - configuration_helpers_mock().get_value.assert_called_once_with( - self.setting_name, - self.setting, - ) - import_module_mock.assert_not_called() - getattr_mock.assert_not_called() - getattr_mock().assert_not_called() - validate_mock.assert_not_called() - - @log_capture() - def test_with_exception( - self, - log_mock: LogCaptureForDecorator, - get_content_items_choices_mock: MagicMock, # pylint: disable=unused-argument - validate_mock: MagicMock, - getattr_mock: MagicMock, - import_module_mock: MagicMock, - configuration_helpers_mock: MagicMock, - ): - """Test with Exception.""" - import_module_mock.side_effect = Exception('example-error-message') - configuration_helpers_mock().get_value.return_value = self.setting_value - - self.assertEqual( - self.form_class(**self.form_kwargs).get_content_items_from_provider(), - [], - ) - configuration_helpers_mock().get_value.assert_called_once_with( - self.setting_name, - self.setting, - ) - import_module_mock.assert_called_once_with(self.setting_module) - getattr_mock.assert_not_called() - getattr_mock().assert_not_called() - validate_mock.assert_not_called() - log_extra = { - 'setting': configuration_helpers_mock().get_value(), - 'exception': str(import_module_mock.side_effect), - } - log_mock.check( - ( - MODULE_PATH, - 'ERROR', - f'Error obtaining content items from provider: {log_extra}', - ), + deep_link_resource_mock().set_url.assert_called_once_with( + self.content_item['url'], ) diff --git a/openedx_lti_tool_plugin/deep_linking/tests/test_views.py b/openedx_lti_tool_plugin/deep_linking/tests/test_views.py index 3a970e6..3c1d11f 100644 --- a/openedx_lti_tool_plugin/deep_linking/tests/test_views.py +++ b/openedx_lti_tool_plugin/deep_linking/tests/test_views.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock, PropertyMock, patch from uuid import uuid4 +from django.conf import settings from django.http.response import Http404 from django.test import RequestFactory, TestCase, override_settings from django.urls import reverse @@ -127,7 +128,7 @@ def test_class_attributes(self): @patch.object(DeepLinkingFormView, 'get_message_from_cache') @patch(f'{MODULE_PATH}.validate_deep_linking_message') -@patch.object(DeepLinkingFormView, 'form_class') +@patch(f'{MODULE_PATH}.configuration_helpers') class TestDeepLinkingFormViewGet(TestCase): """Test DeepLinkingFormView.get method.""" @@ -144,7 +145,7 @@ def setUp(self): def test_with_deep_linking_request( self, render_mock: MagicMock, - form_class_mock: MagicMock, + configuration_helpers_mock: MagicMock, validate_deep_linking_message_mock: MagicMock, get_message_from_cache_mock: MagicMock, ): @@ -155,17 +156,14 @@ def test_with_deep_linking_request( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_called_once_with(get_message_from_cache_mock()) - get_message_from_cache_mock().get_launch_data.assert_called_once_with() - form_class_mock.assert_called_once_with( - request=self.request, - launch_data=get_message_from_cache_mock().get_launch_data(), + configuration_helpers_mock().get_value.assert_called_once_with( + 'OLTITP_DEEP_LINKING_FORM_TEMPLATE', + settings.OLTITP_DEEP_LINKING_FORM_TEMPLATE, ) render_mock.assert_called_once_with( self.request, - 'openedx_lti_tool_plugin/deep_linking/form.html', + configuration_helpers_mock().get_value(), { - 'form': form_class_mock(), - 'form_url': f'{app_config.name}:1.3:deep-linking:form', 'launch_id': self.launch_id, }, ) @@ -174,7 +172,7 @@ def test_with_deep_linking_request( def test_with_lti_exception( self, http_response_error_mock: MagicMock, - form_class_mock: MagicMock, + configuration_helpers_mock: MagicMock, validate_deep_linking_message_mock: MagicMock, get_message_from_cache_mock: MagicMock, ): @@ -188,15 +186,14 @@ def test_with_lti_exception( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_not_called() - get_message_from_cache_mock.return_value.get_launch_data.assert_not_called() - form_class_mock.assert_not_called() + configuration_helpers_mock().get_value.assert_not_called() http_response_error_mock.assert_called_once_with(exception) @patch.object(DeepLinkingFormView, 'http_response_error') def test_with_deep_linking_exception( self, http_response_error_mock: MagicMock, - form_class_mock: MagicMock, + configuration_helpers_mock: MagicMock, validate_deep_linking_message_mock: MagicMock, get_message_from_cache_mock: MagicMock, ): @@ -210,8 +207,7 @@ def test_with_deep_linking_exception( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_called_once_with(get_message_from_cache_mock()) - get_message_from_cache_mock().get_launch_data.assert_not_called() - form_class_mock.assert_not_called() + configuration_helpers_mock().get_value.assert_not_called() http_response_error_mock.assert_called_once_with(exception) @override_settings(OLTITP_ENABLE_LTI_TOOL=False) @@ -260,12 +256,7 @@ def test_with_deep_linking_request( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_called_once_with(get_message_from_cache_mock()) - get_message_from_cache_mock().get_launch_data.assert_called_once_with() - form_class_mock.assert_called_once_with( - self.request.POST, - request=self.request, - launch_data=get_message_from_cache_mock().get_launch_data(), - ) + form_class_mock.assert_called_once_with(self.request.POST) form_class_mock().is_valid.assert_called_once_with() get_message_from_cache_mock().get_deep_link.assert_called_once_with() get_message_from_cache_mock().get_deep_link().output_response_form.assert_called_once_with( @@ -292,12 +283,7 @@ def test_with_invalid_form( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_called_once_with(get_message_from_cache_mock()) - get_message_from_cache_mock().get_launch_data.assert_called_once_with() - form_class_mock.assert_called_once_with( - self.request.POST, - request=self.request, - launch_data=get_message_from_cache_mock().get_launch_data(), - ) + form_class_mock.assert_called_once_with(self.request.POST) form_class_mock().is_valid.assert_called_once_with() get_message_from_cache_mock().get_deep_link.assert_not_called() get_message_from_cache_mock().get_deep_link().output_response_form.assert_not_called() @@ -321,7 +307,6 @@ def test_with_lti_exception( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_not_called() - get_message_from_cache_mock.return_value.get_launch_data.assert_not_called() form_class_mock.assert_not_called() form_class_mock().is_valid.assert_not_called() get_message_from_cache_mock.return_value.get_deep_link.assert_not_called() @@ -346,7 +331,6 @@ def test_with_deep_linking_exception( ) get_message_from_cache_mock.assert_called_once_with(self.request, self.launch_id) validate_deep_linking_message_mock.assert_called_once_with(get_message_from_cache_mock()) - get_message_from_cache_mock().get_launch_data.assert_not_called() form_class_mock.assert_not_called() form_class_mock().is_valid.assert_not_called() get_message_from_cache_mock().get_deep_link.assert_not_called() diff --git a/openedx_lti_tool_plugin/deep_linking/views.py b/openedx_lti_tool_plugin/deep_linking/views.py index a23e78b..22685f2 100644 --- a/openedx_lti_tool_plugin/deep_linking/views.py +++ b/openedx_lti_tool_plugin/deep_linking/views.py @@ -2,6 +2,7 @@ from typing import Union from uuid import uuid4 +from django.conf import settings from django.http import HttpResponse from django.http.request import HttpRequest from django.shortcuts import redirect, render @@ -16,6 +17,7 @@ from openedx_lti_tool_plugin.deep_linking.exceptions import DeepLinkingException from openedx_lti_tool_plugin.deep_linking.forms import DeepLinkingForm from openedx_lti_tool_plugin.deep_linking.utils import validate_deep_linking_message +from openedx_lti_tool_plugin.edxapp_wrapper.site_configuration_module import configuration_helpers from openedx_lti_tool_plugin.http import LoggedHttpResponseBadRequest from openedx_lti_tool_plugin.views import LTIToolView @@ -112,13 +114,11 @@ def get( # Render form template. return render( request, - 'openedx_lti_tool_plugin/deep_linking/form.html', + configuration_helpers().get_value( + 'OLTITP_DEEP_LINKING_FORM_TEMPLATE', + settings.OLTITP_DEEP_LINKING_FORM_TEMPLATE, + ), { - 'form': self.form_class( - request=request, - launch_data=message.get_launch_data(), - ), - 'form_url': f'{app_config.name}:1.3:deep-linking:form', 'launch_id': launch_id, }, ) @@ -149,11 +149,7 @@ def post( # Validate message. validate_deep_linking_message(message) # Initialize form. - form = self.form_class( - request.POST, - request=request, - launch_data=message.get_launch_data(), - ) + form = self.form_class(request.POST) # Validate form. if not form.is_valid(): raise DeepLinkingException(form.errors) diff --git a/openedx_lti_tool_plugin/settings/common.py b/openedx_lti_tool_plugin/settings/common.py index c8cd22b..8946ef4 100644 --- a/openedx_lti_tool_plugin/settings/common.py +++ b/openedx_lti_tool_plugin/settings/common.py @@ -44,7 +44,7 @@ def plugin_settings(settings: LazySettings): settings.OLTITP_ENABLE_LTI_TOOL = False # Deep linking settings - settings.OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER = None + settings.OLTITP_DEEP_LINKING_FORM_TEMPLATE = 'openedx_lti_tool_plugin/deep_linking/form.html' # Backends settings settings.OLTITP_CORE_SIGNALS_BACKEND = f'{BACKENDS_MODULE_PATH}.core_signals_module_o_v1' diff --git a/openedx_lti_tool_plugin/settings/test.py b/openedx_lti_tool_plugin/settings/test.py index d0fd3d8..8863048 100644 --- a/openedx_lti_tool_plugin/settings/test.py +++ b/openedx_lti_tool_plugin/settings/test.py @@ -70,7 +70,7 @@ OLTITP_ENABLE_LTI_TOOL = True # Deep linking settings -OLTITP_DEEP_LINKING_CONTENT_ITEMS_PROVIDER = None +OLTITP_DEEP_LINKING_FORM_TEMPLATE = 'openedx_lti_tool_plugin/deep_linking/form.html' # Backend settings OLTITP_TEST_BACKEND_MODULE_PATH = 'openedx_lti_tool_plugin.tests.backends_for_tests' diff --git a/openedx_lti_tool_plugin/templates/openedx_lti_tool_plugin/base.html b/openedx_lti_tool_plugin/templates/openedx_lti_tool_plugin/base.html index 909b82f..c4a6911 100644 --- a/openedx_lti_tool_plugin/templates/openedx_lti_tool_plugin/base.html +++ b/openedx_lti_tool_plugin/templates/openedx_lti_tool_plugin/base.html @@ -1,11 +1,20 @@
+ {% block head %}