Skip to content

Commit e3bfbcc

Browse files
committed
fixup! refactor: decouple config_id and location
1 parent 14d4f93 commit e3bfbcc

4 files changed

Lines changed: 75 additions & 46 deletions

File tree

lti_consumer/api.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,31 @@
44
Some methods are meant to be used inside the XBlock, so they
55
return plaintext to allow easy testing/mocking.
66
"""
7+
from uuid import UUID
78

89
import json
910

10-
from opaque_keys.edx.keys import CourseKey
11+
from opaque_keys.edx.keys import CourseKey, UsageKey
1112

1213
from lti_consumer.lti_1p3.constants import LTI_1P3_ROLE_MAP
13-
from .models import CourseAllowPIISharingInLTIFlag, LtiConfiguration, LtiDlContentItem
14+
15+
from .filters import get_external_config_from_filter
16+
from .models import CourseAllowPIISharingInLTIFlag, LtiConfiguration, LtiDlContentItem, LtiXBlockConfig
1417
from .utils import (
1518
get_cache_key,
1619
get_data_from_cache,
17-
get_lti_1p3_context_types_claim,
18-
get_lti_deeplinking_content_url,
20+
get_lms_lti_access_token_link,
1921
get_lms_lti_keyset_link,
2022
get_lms_lti_launch_link,
21-
get_lms_lti_access_token_link,
23+
get_lti_1p3_context_types_claim,
24+
get_lti_deeplinking_content_url,
2225
)
23-
from .filters import get_external_config_from_filter
2426

2527

26-
def _get_or_create_local_lti_config(
27-
lti_version,
28-
config_id,
28+
def _get_or_create_local_lti_xblock_config(
29+
lti_version: str,
30+
block_location: UsageKey | str,
31+
config_id: str | None = None,
2932
config_store=LtiConfiguration.CONFIG_ON_XBLOCK,
3033
external_id=None,
3134
):
@@ -39,7 +42,14 @@ def _get_or_create_local_lti_config(
3942
This allows XBlock users to update the LTI version without needing to update the database.
4043
"""
4144
# The create operation is only performed when there is no existing configuration for the block
42-
lti_config, _ = LtiConfiguration.objects.get_or_create(config_id=config_id)
45+
lti_xblock_config, created = LtiXBlockConfig.objects.get_or_create(location=block_location)
46+
if created:
47+
if config_id:
48+
lti_config, _ = LtiConfiguration.objects.get_or_create(config_id=config_id)
49+
else:
50+
lti_config = LtiConfiguration.objects.create()
51+
else:
52+
lti_config = lti_xblock_config.lti_configuration
4353

4454
lti_config.config_store = config_store
4555
lti_config.external_id = external_id
@@ -49,7 +59,7 @@ def _get_or_create_local_lti_config(
4959

5060
lti_config.save()
5161

52-
return lti_config
62+
return lti_xblock_config
5363

5464

5565
def _get_config_by_config_id(config_id):
@@ -59,16 +69,24 @@ def _get_config_by_config_id(config_id):
5969
return LtiConfiguration.objects.get(config_id=config_id)
6070

6171

72+
def _get_config_by_location(location: UsageKey | str):
73+
"""
74+
Gets the LTI xblock config by its UUID config ID
75+
"""
76+
return LtiXBlockConfig.objects.get(location=location)
77+
78+
6279
def _get_lti_config_for_block(block):
6380
"""
64-
Retrieves or creates a LTI Configuration for a block.
81+
Retrieves or creates a LTI Xblock Configuration for a block.
6582
66-
This wraps around `_get_or_create_local_lti_config` and handles the block and modulestore
83+
This wraps around `_get_or_create_local_lti_xblock_config` and handles the block and modulestore
6784
bits of configuration.
6885
"""
6986
if block.config_type == 'database':
70-
lti_config = _get_or_create_local_lti_config(
87+
lti_xblock_config = _get_or_create_local_lti_xblock_config(
7188
block.lti_version,
89+
block.scope_ids.usage_id,
7290
block.config_id,
7391
LtiConfiguration.CONFIG_ON_DB,
7492
)
@@ -77,51 +95,54 @@ def _get_lti_config_for_block(block):
7795
{"course_key": block.scope_ids.usage_id.context_key},
7896
block.external_config
7997
)
80-
lti_config = _get_or_create_local_lti_config(
98+
lti_xblock_config = _get_or_create_local_lti_xblock_config(
8199
config.get("version"),
100+
block.scope_ids.usage_id,
82101
block.config_id,
83102
LtiConfiguration.CONFIG_EXTERNAL,
84103
external_id=block.external_config,
85104
)
86105
else:
87-
lti_config = _get_or_create_local_lti_config(
106+
lti_xblock_config = _get_or_create_local_lti_xblock_config(
88107
block.lti_version,
108+
block.scope_ids.usage_id,
89109
block.config_id,
90110
LtiConfiguration.CONFIG_ON_XBLOCK,
91111
)
92-
return lti_config
112+
return lti_xblock_config
93113

94114

95-
def config_id_for_block(block):
115+
def config_for_block(block):
96116
"""
97117
Returns the externally facing config_id of the LTI Configuration used by this block,
98118
creating one if required. That ID is suitable for use in launch data or get_consumer.
99119
"""
100-
config = _get_lti_config_for_block(block)
101-
return config.config_id
120+
xblock_config = _get_lti_config_for_block(block)
121+
return xblock_config
102122

103123

104-
def get_lti_consumer(config_id):
124+
def get_lti_consumer(location: UsageKey):
105125
"""
106-
Retrieves an LTI Consumer instance for a given configuration.
126+
Retrieves an LTI Consumer instance for a given location.
107127
108128
Returns an instance of LtiConsumer1p1 or LtiConsumer1p3 depending
109129
on the configuration.
110130
"""
111131
# Return an instance of LTI 1.1 or 1.3 consumer, depending
112132
# on the configuration stored in the model.
113-
return _get_config_by_config_id(config_id).get_lti_consumer()
133+
return _get_config_by_location(location).get_lti_consumer()
114134

115135

116136
def get_lti_1p3_launch_info(
117137
launch_data,
138+
location: UsageKey,
118139
):
119140
"""
120141
Retrieves the Client ID, Keyset URL and other urls used to configure a LTI tool.
121142
"""
122143
# Retrieve LTI Config and consumer
123-
lti_config = _get_config_by_config_id(launch_data.config_id)
124-
lti_consumer = lti_config.get_lti_consumer()
144+
lti_xblock_config = _get_config_by_location(location)
145+
lti_consumer = lti_xblock_config.get_lti_consumer()
125146

126147
# Check if deep Linking is available, if so, add some extra context:
127148
# Deep linking launch URL, and if deep linking is already configured
@@ -138,12 +159,13 @@ def get_lti_1p3_launch_info(
138159

139160
# Retrieve LTI Content Items (if any was set up)
140161
dl_content_items = LtiDlContentItem.objects.filter(
141-
lti_configuration=lti_config
162+
lti_xblock_config=lti_xblock_config
142163
)
143164
# Add content item attributes to context
144165
if dl_content_items.exists():
145166
deep_linking_content_items = [item.attributes for item in dl_content_items]
146167

168+
lti_config = lti_xblock_config.lti_configuration
147169
config_id = lti_config.config_id
148170
client_id = lti_config.lti_1p3_client_id
149171

@@ -169,14 +191,15 @@ def get_lti_1p3_launch_info(
169191

170192
def get_lti_1p3_launch_start_url(
171193
launch_data,
194+
location: UsageKey,
172195
deep_link_launch=False,
173196
dl_content_id=None,
174197
):
175198
"""
176199
Computes and retrieves the LTI URL that starts the OIDC flow.
177200
"""
178201
# Retrieve LTI consumer
179-
lti_consumer = get_lti_consumer(launch_data.config_id)
202+
lti_consumer = get_lti_consumer(location)
180203

181204
# Include a message hint in the launch_data depending on LTI launch type
182205
# Case 1: Performs Deep Linking configuration flow. Triggered by staff users to
@@ -194,6 +217,7 @@ def get_lti_1p3_launch_start_url(
194217

195218
def get_lti_1p3_content_url(
196219
launch_data,
220+
location: UsageKey,
197221
):
198222
"""
199223
Computes and returns which URL the LTI consumer should launch to.
@@ -213,13 +237,14 @@ def get_lti_1p3_content_url(
213237

214238
# If there's no content items, return normal LTI launch URL
215239
if not content_items.count():
216-
return get_lti_1p3_launch_start_url(launch_data)
240+
return get_lti_1p3_launch_start_url(launch_data, location)
217241

218242
# If there's a single `ltiResourceLink` content, return the launch
219243
# url for that specific deep link
220244
if content_items.count() == 1 and content_items.get().content_type == LtiDlContentItem.LTI_RESOURCE_LINK:
221245
return get_lti_1p3_launch_start_url(
222246
launch_data,
247+
location,
223248
dl_content_id=content_items.get().id,
224249
)
225250

lti_consumer/lti_xblock.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -715,11 +715,14 @@ def validate(self):
715715
validation.add(ValidationMessage(ValidationMessage.WARNING, str(
716716
_("The specified LTI ID is not configured in this course's Advanced Settings.")
717717
)))
718+
return validation
719+
720+
def save(self):
718721
if self.lti_version == "lti_1p3" and not self.config_id:
719722
self.config_id = str(uuid.uuid4())
720723
# __AUTO_GENERATED_PRINT_VAR_START__
721724
print(f"""======================================= LtiConsumerXBlock#validate config_id: {self.config_id}""") # __AUTO_GENERATED_PRINT_VAR_END__
722-
return validation
725+
super().save()
723726

724727
def get_settings(self):
725728
"""
@@ -1126,9 +1129,9 @@ def _get_lti_consumer(self):
11261129
# Runtime import since this will only run in the
11271130
# Open edX LMS/Studio environments.
11281131
# pylint: disable=import-outside-toplevel
1129-
from lti_consumer.api import config_id_for_block, get_lti_consumer
1132+
from lti_consumer.api import config_for_block
11301133

1131-
return get_lti_consumer(config_id_for_block(self))
1134+
return config_for_block(self).get_lti_consumer()
11321135

11331136
def extract_real_user_data(self):
11341137
"""
@@ -1654,8 +1657,8 @@ def get_lti_1p3_launch_data(self):
16541657
# Open edX LMS/Studio environments.
16551658
# TODO: Replace this with a more appropriate API function that is intended for public use.
16561659
# pylint: disable=import-outside-toplevel
1657-
from lti_consumer.api import config_id_for_block
1658-
config_id = config_id_for_block(self)
1660+
from lti_consumer.api import config_for_block
1661+
xblock_config = config_for_block(self)
16591662

16601663
location = self.scope_ids.usage_id
16611664
course_key = str(location.context_key)
@@ -1678,7 +1681,7 @@ def get_lti_1p3_launch_data(self):
16781681
launch_data = Lti1p3LaunchData(
16791682
user_id=self.lms_user_id,
16801683
user_role=self.role,
1681-
config_id=config_id,
1684+
config_id=xblock_config.config.config_id,
16821685
resource_link_id=str(location),
16831686
external_user_id=self.external_user_id,
16841687
preferred_username=username,
@@ -1719,6 +1722,7 @@ def _get_lti_block_launch_handler(self):
17191722
from lti_consumer.api import get_lti_1p3_content_url # pylint: disable=import-outside-toplevel
17201723
lti_block_launch_handler = get_lti_1p3_content_url(
17211724
launch_data,
1725+
self.scope_ids.usage_id,
17221726
)
17231727

17241728
return lti_block_launch_handler

lti_consumer/tests/unit/test_api.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
from lti_consumer.api import (
1313
_get_config_by_config_id,
14-
_get_or_create_local_lti_config,
15-
config_id_for_block,
14+
_get_or_create_local_lti_xblock_config,
15+
config_for_block,
1616
get_end_assessment_return,
1717
get_lti_1p3_content_url,
1818
get_deep_linking_data,
@@ -103,13 +103,13 @@ def setUp(self):
103103

104104
def test_double_fetch(self):
105105
self.xblock.config_type = 'database'
106-
config_id = config_id_for_block(self.xblock)
106+
config_id = config_for_block(self.xblock)
107107
self.assertIsNotNone(config_id)
108108
config = _get_config_by_config_id(config_id)
109109
self.assertEqual(LtiConfiguration.CONFIG_ON_DB, config.config_store)
110110

111111
# fetch again, shouldn't make a new one
112-
second_config_id = config_id_for_block(self.xblock)
112+
second_config_id = config_for_block(self.xblock)
113113
self.assertEqual(config_id, second_config_id)
114114

115115
@ddt.data(
@@ -122,7 +122,7 @@ def test_store_types(self, mapping_pair, mock_external_config):
122122
mock_external_config.return_value = {"version": LtiConfiguration.LTI_1P3}
123123
str_store, result_store = mapping_pair
124124
self.xblock.config_type = str_store
125-
config_id = config_id_for_block(self.xblock)
125+
config_id = config_for_block(self.xblock)
126126
self.assertIsNotNone(config_id)
127127
config = _get_config_by_config_id(config_id)
128128
self.assertEqual(result_store, config.config_store)
@@ -144,7 +144,7 @@ def test_create_lti_config_if_inexistent(self):
144144
self.assertEqual(LtiConfiguration.objects.all().count(), 0)
145145

146146
# Call API
147-
lti_config = _get_or_create_local_lti_config(
147+
lti_config = _get_or_create_local_lti_xblock_config(
148148
lti_version=lti_version,
149149
block_location=location
150150
)
@@ -166,7 +166,7 @@ def test_retrieve_existing(self):
166166
)
167167

168168
# Call API
169-
lti_config_retrieved = _get_or_create_local_lti_config(
169+
lti_config_retrieved = _get_or_create_local_lti_xblock_config(
170170
lti_version=lti_version,
171171
block_location=location
172172
)
@@ -187,7 +187,7 @@ def test_update_lti_version(self):
187187
)
188188

189189
# Call API
190-
_get_or_create_local_lti_config(
190+
_get_or_create_local_lti_xblock_config(
191191
lti_version=LtiConfiguration.LTI_1P3,
192192
block_location=location
193193
)
@@ -204,7 +204,7 @@ def test_create_lti_config_config_store(self, config_store):
204204
"""
205205
location = 'block-v1:course+test+2020+type@problem+block@test'
206206
lti_version = LtiConfiguration.LTI_1P3
207-
lti_config = _get_or_create_local_lti_config(
207+
lti_config = _get_or_create_local_lti_xblock_config(
208208
lti_version=lti_version,
209209
block_location=location,
210210
config_store=config_store,
@@ -226,7 +226,7 @@ def test_external_config_values_are_cleared(self):
226226
external_id="test_plugin:test-id"
227227
)
228228

229-
_get_or_create_local_lti_config(
229+
_get_or_create_local_lti_xblock_config(
230230
lti_version=lti_version,
231231
block_location=location,
232232
external_id=None

lti_consumer/tests/unit/test_lti_xblock.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from jwt.api_jwk import PyJWK, PyJWKSet
2121
from lti_consumer.exceptions import LtiError
2222

23-
from lti_consumer.api import config_id_for_block
23+
from lti_consumer.api import config_for_block
2424
from lti_consumer.data import Lti1p3LaunchData
2525
from lti_consumer.lti_xblock import LtiConsumerXBlock, parse_handler_suffix, valid_config_type_values
2626
from lti_consumer.lti_1p3.tests.utils import create_jwt
@@ -1895,7 +1895,7 @@ def test_get_lti_1p3_launch_data(
18951895
expected_launch_data_kwargs = {
18961896
"user_id": 1,
18971897
"user_role": "instructor",
1898-
"config_id": config_id_for_block(self.xblock),
1898+
"config_id": config_for_block(self.xblock),
18991899
"resource_link_id": str(self.xblock.scope_ids.usage_id),
19001900
"external_user_id": "external_user_id",
19011901
"launch_presentation_document_target": "iframe",

0 commit comments

Comments
 (0)