Skip to content

Commit

Permalink
feat: syncrhonize restricted course runs
Browse files Browse the repository at this point in the history
ENT-9570
  • Loading branch information
iloveagent57 committed Oct 17, 2024
1 parent d4c8e76 commit cf4d779
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 16 deletions.
55 changes: 47 additions & 8 deletions enterprise_catalog/apps/catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,27 +861,40 @@ def store_canonical_record(cls, course_metadata_dict):
@classmethod
def store_record_with_query(cls, course_metadata_dict, catalog_query):
filtered_metadata = cls.filter_restricted_runs(course_metadata_dict, catalog_query)
return cls._store_record(filtered_metadata, catalog_query)
course_record = cls._store_record(filtered_metadata, catalog_query)
for course_run_dict in cls.restricted_runs_for_course(filtered_metadata, catalog_query):
course_run_record, _ = ContentMetadata.objects.update_or_create(
content_key=course_run_dict['key'],
content_type=COURSE_RUN,
content_uuid=course_run_dict['uuid'],
defaults={
'_json_metadata': course_run_dict,
}
)
RestrictedRunAllowedForRestrictedCourse.objects.get_or_create(
course=course_record, run=course_run_record,
)
return course_record

@classmethod
def filter_restricted_runs(cls, course_metadata_dict, catalog_query):
"""
Returns a copy of ``course_metadata_dict`` whose course_runs list
contains only unrestricted runs and restricted runs that are allowed
by the provided ``catalog_query``.
by the provided ``catalog_query``, and whose ``course_runs_keys``,
``course_run_statuses``, and ``first_enrollable_paid_seat_price`` items
are updated to take only these allowed runs into account.
"""
filtered_metadata = copy.deepcopy(course_metadata_dict)
allowed_restricted_runs = catalog_query.restricted_runs_allowed.get(course_metadata_dict['key'], [])

allowed_runs = []
allowed_statuses = set()
allowed_keys = []

for run in filtered_metadata['course_runs']:
if run.get(COURSE_RUN_RESTRICTION_TYPE_KEY) is None or run['key'] in allowed_restricted_runs:
allowed_runs.append(run)
allowed_statuses.add(run['status'])
allowed_keys.append(run['key'])
for run in cls.allowed_runs_for_course(filtered_metadata, catalog_query):
allowed_runs.append(run)
allowed_statuses.add(run['status'])
allowed_keys.append(run['key'])

filtered_metadata['course_runs'] = allowed_runs
filtered_metadata['course_run_keys'] = allowed_keys
Expand All @@ -892,6 +905,32 @@ def filter_restricted_runs(cls, course_metadata_dict, catalog_query):

return filtered_metadata

@staticmethod
def allowed_runs_for_course(course_metadata_dict, catalog_query):
"""
Given a ``course_metadata_dict``, returns a filtered list of ``course_runs``
containing only unrestricted runs and restricted runs that are allowed by
the provided ``catalog_query``.
"""
allowed_restricted_runs = catalog_query.restricted_runs_allowed.get(course_metadata_dict['key'], [])
return [
run for run in course_metadata_dict['course_runs']
if run.get(COURSE_RUN_RESTRICTION_TYPE_KEY) is None or run['key'] in allowed_restricted_runs
]

@staticmethod
def restricted_runs_for_course(course_metadata_dict, catalog_query):
"""
Given a ``course_metadata_dict``, returns a filtered list of ``course_runs``
containing only restricted runs that are allowed by
the provided ``catalog_query``.
"""
allowed_restricted_runs = catalog_query.restricted_runs_allowed.get(course_metadata_dict['key'], [])
return [
run for run in course_metadata_dict['course_runs']
if run['key'] in allowed_restricted_runs
]


class RestrictedRunAllowedForRestrictedCourse(TimeStampedModel):
"""
Expand Down
8 changes: 8 additions & 0 deletions enterprise_catalog/apps/catalog/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,18 +1238,21 @@ def test_store_record_with_query(self):
'key': 'course-v1:edX+course+run1',
'is_restricted': False,
'status': 'published',
'uuid': str(uuid4()),
},
{
'key': 'course-v1:edX+course+run2',
'is_restricted': True,
COURSE_RUN_RESTRICTION_TYPE_KEY: RESTRICTION_FOR_B2B,
'status': 'unpublished',
'uuid': str(uuid4()),
},
{
'key': 'course-v1:edX+course+run3',
'is_restricted': True,
COURSE_RUN_RESTRICTION_TYPE_KEY: RESTRICTION_FOR_B2B,
'status': 'other',
'uuid': str(uuid4()),
},
],
}
Expand All @@ -1270,12 +1273,14 @@ def test_store_record_with_query(self):
'key': 'course-v1:edX+course+run1',
'is_restricted': False,
'status': 'published',
'uuid': content_metadata_dict['course_runs'][0]['uuid'],
},
{
'key': 'course-v1:edX+course+run2',
'is_restricted': True,
COURSE_RUN_RESTRICTION_TYPE_KEY: RESTRICTION_FOR_B2B,
'status': 'unpublished',
'uuid': content_metadata_dict['course_runs'][1]['uuid']
},
],
)
Expand Down Expand Up @@ -1338,18 +1343,21 @@ def test_synchronize_restricted_content(self, mock_client):
'key': 'course-v1:edX+course+run1',
'is_restricted': False,
'status': 'published',
'uuid': str(uuid4()),
},
{
'key': 'course-v1:edX+course+run2',
'is_restricted': True,
COURSE_RUN_RESTRICTION_TYPE_KEY: RESTRICTION_FOR_B2B,
'status': 'unpublished',
'uuid': str(uuid4()),
},
{
'key': 'course-v1:edX+course+run3',
'is_restricted': True,
COURSE_RUN_RESTRICTION_TYPE_KEY: RESTRICTION_FOR_B2B,
'status': 'other',
'uuid': str(uuid4()),
},
],
}
Expand Down
15 changes: 7 additions & 8 deletions enterprise_catalog/apps/curation/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def title(self):
"""
if not self.content_metadata:
return None
return self.content_metadata.json_metadata.get('title') # pylint: disable=no-member
return self.content_metadata.json_metadata.get('title')

@property
def course_run_statuses(self):
Expand All @@ -215,7 +215,7 @@ def course_run_statuses(self):
"""
if not self.content_metadata:
return None
return self.content_metadata.json_metadata.get('course_run_statuses') # pylint: disable=no-member
return self.content_metadata.json_metadata.get('course_run_statuses')

@property
def card_image_url(self):
Expand All @@ -232,14 +232,13 @@ def card_image_url(self):
# aside: pylint doesn't know that self.content_metadata.json_metadata is dict-like, so we have to silence all
# the warnings.
if content_type == COURSE:
return self.content_metadata.json_metadata.get('image_url') # pylint: disable=no-member
return self.content_metadata.json_metadata.get('image_url')
if content_type == COURSE_RUN:
return self.content_metadata.json_metadata.get('image_url') # pylint: disable=no-member
return self.content_metadata.json_metadata.get('image_url')
elif content_type == PROGRAM:
return self.content_metadata.json_metadata.get('card_image_url') # pylint: disable=no-member
return self.content_metadata.json_metadata.get('card_image_url')
elif content_type == LEARNER_PATHWAY:
try:
# pylint: disable=invalid-sequence-index
return self.content_metadata.json_metadata['card_image']['card']['url']
except (KeyError, TypeError):
# KeyError covers the case where any of the keys along the path are missing,
Expand Down Expand Up @@ -271,9 +270,9 @@ def authoring_organizations(self):
content_type = self.content_type
owners = []
if content_type == COURSE:
owners = self.content_metadata.json_metadata.get('owners') # pylint: disable=no-member
owners = self.content_metadata.json_metadata.get('owners')
elif content_type == PROGRAM:
owners = self.content_metadata.json_metadata.get('authoring_organizations') # pylint: disable=no-member
owners = self.content_metadata.json_metadata.get('authoring_organizations')

return [
{
Expand Down

0 comments on commit cf4d779

Please sign in to comment.