Skip to content

Commit 942bd29

Browse files
Merge pull request #633 from openedx/ENT-9890/fix-course-run-metadata
fix: use course-run specific metadata
2 parents 8b16678 + 6993ab3 commit 942bd29

File tree

4 files changed

+142
-5
lines changed

4 files changed

+142
-5
lines changed

enterprise_access/apps/content_assignments/tasks.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from enterprise_access.tasks import LoggedTaskWithRetry
2121
from enterprise_access.utils import (
2222
get_automatic_expiration_date_and_reason,
23+
get_course_run_metadata_for_assignment,
2324
get_normalized_metadata_for_assignment,
2425
localized_utcnow
2526
)
@@ -217,14 +218,14 @@ def get_start_date(self):
217218
"""
218219
start_date = self.normalized_metadata.get('start_date')
219220
end_date = self.normalized_metadata.get('end_date')
220-
course_metadata = self.course_metadata
221+
course_run_metadata = get_course_run_metadata_for_assignment(self.assignment, self.course_metadata)
221222
logger.info(
222223
f"[get_start_date] Assignment UUID: {self.assignment.uuid} - start_date: {start_date}, "
223224
f"end_date: {end_date}, "
224-
f"course_metadata: {course_metadata}"
225+
f"course_run_metadata: {course_run_metadata}"
225226
)
226227
return get_human_readable_date(
227-
get_self_paced_normalized_start_date(start_date, end_date, course_metadata)
228+
get_self_paced_normalized_start_date(start_date, end_date, course_run_metadata)
228229
)
229230

230231
def get_action_required_by_timestamp(self):

enterprise_access/apps/content_assignments/tests/test_tasks.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,17 @@ def setUpTestData(cls):
232232
}
233233
cls.mock_content_metadata = {
234234
'key': TEST_COURSE_KEY,
235+
'course_runs': [
236+
{
237+
'key': TEST_COURSE_RUN_KEY,
238+
'start': '2020-01-01T12:00:00Z',
239+
'end': '2022-01-01 12:00:00Z',
240+
'enrollment_start': None,
241+
'enrollment_end': '2023-09-11T19:00:00Z',
242+
'pacing_type': 'self_paced',
243+
'weeks_to_complete': 8,
244+
}
245+
],
235246
'normalized_metadata': {
236247
'start_date': '2020-01-01T12:00:00Z',
237248
'end_date': '2022-01-01 12:00:00Z',
@@ -275,6 +286,7 @@ def setUp(self):
275286
parent_content_key=TEST_COURSE_KEY,
276287
is_assigned_course_run=True,
277288
learner_email='[email protected]',
289+
preferred_course_run_key=TEST_COURSE_RUN_KEY,
278290
lms_user_id=TEST_LMS_USER_ID_2,
279291
assignment_configuration=self.assignment_configuration,
280292
)
@@ -403,7 +415,18 @@ def test_send_reminder_email_for_pending_assignment(
403415
{'name': 'Good People', 'logo_image_url': 'http://pictures.nice'},
404416
{'name': 'Fast Learners', 'logo_image_url': 'http://pictures.totally'},
405417
],
406-
'card_image_url': 'https://itsanimage.com'
418+
'card_image_url': 'https://itsanimage.com',
419+
'course_runs': [
420+
{
421+
'key': TEST_COURSE_RUN_KEY,
422+
'start': '2020-01-01 12:00:00Z',
423+
'end': '2022-01-01 12:00:00Z',
424+
'enrollment_start': None,
425+
'enrollment_end': '2023-09-11T19:00:00Z',
426+
'pacing_type': 'self_paced',
427+
'weeks_to_complete': 8,
428+
}
429+
],
407430
}
408431
mock_catalog_client.return_value.catalog_content_metadata.return_value = {
409432
'count': 1,

enterprise_access/apps/content_assignments/tests/test_utils.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Tests for Enterprise Access content_assignments utils.
33
"""
44

5+
import uuid
6+
57
import ddt
68
from django.test import TestCase
79

@@ -13,7 +15,12 @@
1315
has_time_to_complete,
1416
is_within_minimum_start_date_threshold
1517
)
16-
from enterprise_access.utils import _curr_date, _days_from_now, get_normalized_metadata_for_assignment
18+
from enterprise_access.utils import (
19+
_curr_date,
20+
_days_from_now,
21+
get_course_run_metadata_for_assignment,
22+
get_normalized_metadata_for_assignment
23+
)
1724

1825
mock_course_run_1 = {
1926
'start_date': _days_from_now(-370, DATE_FORMAT_ISO_8601),
@@ -39,6 +46,9 @@
3946
'content_price': 100,
4047
}
4148

49+
assignment_uuid = uuid.uuid4()
50+
advertised_course_run_uuid = uuid.uuid4()
51+
4252

4353
@ddt.ddt
4454
class UtilsTests(TestCase):
@@ -273,3 +283,66 @@ def test_get_normalized_metadata_for_assignment(self, assignment, expected_outpu
273283
content_metadata=self.mock_content_metadata
274284
)
275285
self.assertEqual(normalized_metadata, expected_output)
286+
287+
@ddt.data(
288+
# Test case for preferred course run that exists
289+
{
290+
'assignment': {
291+
'preferred_course_run_key': 'course-v1:edX+DemoX+1T60',
292+
'uuid': assignment_uuid
293+
},
294+
'content_metadata': {
295+
'course_runs': [
296+
{'key': 'course-v1:edX+DemoX+1T60', 'data': 'test1'},
297+
{'key': 'course-v1:edX+DemoX+1T360', 'data': 'test2'}
298+
],
299+
'advertised_course_run_uuid': advertised_course_run_uuid,
300+
'key': 'test-content-key'
301+
},
302+
'expected_output': {'key': 'course-v1:edX+DemoX+1T60', 'data': 'test1'}
303+
},
304+
# Test case for preferred course run that doesn't exist - should fall back to advertised run
305+
{
306+
'assignment': {
307+
'preferred_course_run_key': 'non-existent-key',
308+
'uuid': assignment_uuid
309+
},
310+
'content_metadata': {
311+
'course_runs': [
312+
{'key': 'course-v1:edX+DemoX+1T60', 'uuid': advertised_course_run_uuid, 'data': 'test1'},
313+
{'key': 'course-v1:edX+DemoX+1T360', 'data': 'test2'}
314+
],
315+
'advertised_course_run_uuid': advertised_course_run_uuid,
316+
'key': 'test-content-key'
317+
},
318+
'expected_output': {'key': 'course-v1:edX+DemoX+1T60', 'uuid': advertised_course_run_uuid, 'data': 'test1'}
319+
},
320+
# Test case for no preferred course run - should return advertised run
321+
{
322+
'assignment': {
323+
'preferred_course_run_key': None,
324+
'uuid': assignment_uuid
325+
},
326+
'content_metadata': {
327+
'course_runs': [
328+
{'key': 'course-v1:edX+DemoX+1T60', 'uuid': advertised_course_run_uuid, 'data': 'test1'},
329+
{'key': 'course-v1:edX+DemoX+1T360', 'data': 'test2'}
330+
],
331+
'advertised_course_run_uuid': advertised_course_run_uuid,
332+
'key': 'test-content-key'
333+
},
334+
'expected_output': {'key': 'course-v1:edX+DemoX+1T60', 'uuid': advertised_course_run_uuid, 'data': 'test1'}
335+
}
336+
)
337+
@ddt.unpack
338+
def test_get_course_run_metadata_for_assignment(self, assignment, content_metadata, expected_output):
339+
"""
340+
Test get_course_run_metadata_for_assignment returns the correct course run metadata
341+
based on assignment and content metadata.
342+
"""
343+
assignment_obj = LearnerContentAssignmentFactory(**assignment)
344+
course_run_metadata = get_course_run_metadata_for_assignment(
345+
assignment=assignment_obj,
346+
content_metadata=content_metadata
347+
)
348+
self.assertEqual(course_run_metadata, expected_output)

enterprise_access/utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,43 @@ def _days_from_now(days_from_now=0, date_format=None):
245245
if not date_format:
246246
return date
247247
return date.strftime(date_format)
248+
249+
250+
def get_advertised_course_run_metadata(content_metadata):
251+
course_runs = content_metadata.get('course_runs', [])
252+
advertised_course_run_uuid = content_metadata.get('advertised_course_run_uuid')
253+
return next((run for run in course_runs if run.get('uuid') == advertised_course_run_uuid), None)
254+
255+
256+
def get_course_run_metadata_for_assignment(assignment, content_metadata):
257+
"""
258+
Retrieves metadata for a specific course run associated with an assignment. If the assignment has
259+
a preferred course run, returns the metadata for that run. If the preferred run metadata is not
260+
found, returns normalized_metadata.
261+
262+
Args:
263+
assignment (dict): The assignment object.
264+
content_metadata (dict): The content metadata object.
265+
266+
Returns:
267+
dict: Course run metadata if available, otherwise advertised_course_run.
268+
"""
269+
course_runs = content_metadata.get('course_runs', [])
270+
271+
# For run-based assignments, return metadata for the preferred course run if
272+
# available. If not, fallback to advertised run.
273+
if preferred_course_run_key := assignment.preferred_course_run_key:
274+
course_run = next((run for run in course_runs if run.get('key') == preferred_course_run_key), None)
275+
if not course_run:
276+
logger.warning(
277+
'Metadata not found for preferred course run key %s in content metadata %s. Assignment UUID: %s',
278+
preferred_course_run_key,
279+
content_metadata.get('key'),
280+
assignment.uuid
281+
)
282+
# Fallback to advertised course run if preferred course run metadata is missing
283+
return get_advertised_course_run_metadata(content_metadata)
284+
return course_run
285+
286+
# For course-based assignments, return metadata for the advertised course run
287+
return get_advertised_course_run_metadata(content_metadata)

0 commit comments

Comments
 (0)