Skip to content

Commit

Permalink
squash
Browse files Browse the repository at this point in the history
  • Loading branch information
pwnage101 committed Oct 9, 2024
1 parent da06b5f commit 46d70a8
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.16 on 2024-10-08 22:19
# Generated by Django 4.2.16 on 2024-10-09 03:45

import collections
from django.conf import settings
Expand Down Expand Up @@ -62,13 +62,13 @@ class Migration(migrations.Migration):
field=jsonfield.fields.JSONField(default=dict, dump_kwargs={'cls': jsonfield.encoder.JSONEncoder, 'ensure_ascii': False, 'indent': 4, 'separators': (',', ':')}, help_text="Query parameters which will be used to filter the discovery service's search/all endpoint results, specified as a JSON object.", load_kwargs={'object_pairs_hook': collections.OrderedDict}),
),
migrations.CreateModel(
name='RestrictedRunsAllowedForRestrictedCourses',
name='RestrictedRunAllowedForRestrictedCourse',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('course', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='restricted_runs_allowed_for_restricted_courses', to='catalog.restrictedcoursemetadata')),
('run', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='restricted_runs_allowed_for_restricted_courses', to='catalog.contentmetadata')),
('course', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='restricted_run_allowed_for_restricted_course', to='catalog.restrictedcoursemetadata')),
('run', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='restricted_run_allowed_for_restricted_course', to='catalog.contentmetadata')),
],
options={
'abstract': False,
Expand Down
48 changes: 26 additions & 22 deletions enterprise_catalog/apps/catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,12 +402,12 @@ def content_metadata(self):
if not self.catalog_query:
return ContentMetadata.objects.none()
return self.catalog_query.contentmetadata_set.prefetch_related(
'restricted_runs_allowed_for_restricted_courses'
'restricted_run_allowed_for_restricted_course'
).filter(
# Exclude all restricted runs (heuristic is that a run is assumed
# restricted if it is mapped to a restricted course via
# RestrictedRunsAllowedForRestrictedCourses).
restricted_runs_allowed_for_restricted_courses__isnull=True,
# RestrictedRunAllowedForRestrictedCourse).
restricted_run_allowed_for_restricted_course__isnull=True,
)

@property
Expand All @@ -430,13 +430,17 @@ def content_metadata_with_restricted(self):
"""
if not self.catalog_query:
return ContentMetadata.objects.none()
# FYI: prefetch causes a performance penalty by introducing a 2nd database query.
prefetch_qs = models.Prefetch(
'restricted_courses',
queryset=RestrictedCourseMetadata.objects.filter(catalog_query=self.catalog_query),
to_attr='restricted_course_metadata_for_catalog_query',
)
return self.catalog_query.contentmetadata_set.prefetch_related(prefetch_qs)
related_contentmetadata = self.catalog_query.contentmetadata_set
# Provide json_metadata overrides via dynamic attribute if any restricted runs are allowed.
if self.catalog_query.restricted_runs_allowed:
# FYI: prefetch causes a performance penalty by introducing a 2nd database query.
prefetch_qs = models.Prefetch(
'restricted_courses',
queryset=RestrictedCourseMetadata.objects.filter(catalog_query=self.catalog_query),
to_attr='restricted_course_metadata_for_catalog_query',
)
related_contentmetadata = related_contentmetadata.prefetch_related(prefetch_qs)
return related_contentmetadata.all()

@cached_property
def restricted_runs_allowed(self):
Expand Down Expand Up @@ -540,23 +544,23 @@ def get_matching_content(self, content_keys, include_restricted=False):
# parent content keys, i.e. course ids associated with the specified content_keys
# (if any) to handle the following case:
# - catalog contains courses and the specified content_keys are course run ids.
searched_metadata = ContentMetadata.objects.filter(content_key__in=content_keys).prefetch_related(
'restricted_runs_allowed_for_restricted_courses'
)
if include_restricted:
searched_metadata = ContentMetadata.objects.filter(content_key__in=content_keys)
if include_restricted and self.catalog_query.restricted_runs_allowed:
# Only hide restricted runs that are not allowed by the current catalog.
searched_metadata = searched_metadata.exclude(
searched_metadata = searched_metadata.prefetch_related(
'restricted_run_allowed_for_restricted_course'
).exclude(
# Find all restricted runs allowed by a RestrictedCourseMetadata related to the
# current CatalogQuery. Do NOT exclude those.
~Q(restricted_runs_allowed_for_restricted_courses__course__catalog_query=self.catalog_query)
~Q(restricted_run_allowed_for_restricted_course__course__catalog_query=self.catalog_query)
# Exclude all other restricted runs. A run is assumed of type restricted if it
# is related to at least one RestrictedRunsAllowedForRestrictedCourses.
& Q(restricted_runs_allowed_for_restricted_courses__isnull=False)
# is related to at least one RestrictedRunAllowedForRestrictedCourse.
& Q(restricted_run_allowed_for_restricted_course__isnull=False)
)
else:
# Hide ALL restricted runs.
searched_metadata = searched_metadata.exclude(
restricted_runs_allowed_for_restricted_courses__isnull=False
restricted_run_allowed_for_restricted_course__isnull=False
)
parent_content_keys = {
metadata.parent_content_key
Expand Down Expand Up @@ -964,7 +968,7 @@ class Meta:
history = HistoricalRecords()


class RestrictedRunsAllowedForRestrictedCourses(TimeStampedModel):
class RestrictedRunAllowedForRestrictedCourse(TimeStampedModel):
"""
Mapping table to relate RestrictedCourseMetadata objects to restricted runs in ContentMetadata.
Expand All @@ -978,14 +982,14 @@ class RestrictedRunsAllowedForRestrictedCourses(TimeStampedModel):
RestrictedCourseMetadata,
blank=False,
null=True,
related_name='restricted_runs_allowed_for_restricted_courses',
related_name='restricted_run_allowed_for_restricted_course',
on_delete=models.deletion.SET_NULL,
)
run = models.ForeignKey(
ContentMetadata,
blank=False,
null=True,
related_name='restricted_runs_allowed_for_restricted_courses',
related_name='restricted_run_allowed_for_restricted_course',
on_delete=models.deletion.SET_NULL,
)

Expand Down
27 changes: 27 additions & 0 deletions enterprise_catalog/apps/catalog/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
EnterpriseCatalog,
EnterpriseCatalogFeatureRole,
EnterpriseCatalogRoleAssignment,
RestrictedCourseMetadata,
RestrictedRunAllowedForRestrictedCourse,
)
from enterprise_catalog.apps.core.models import User

Expand Down Expand Up @@ -184,6 +186,31 @@ def _json_metadata(self):
return json_metadata


class RestrictedCourseMetadataFactory(factory.django.DjangoModelFactory):
"""
Test factory for the `RestrictedCourseMetadata` model.
"""
class Meta:
model = RestrictedCourseMetadata

content_key = factory.Faker('bothify', text='??????????+####')
content_uuid = factory.LazyFunction(uuid4)
content_type = COURSE
parent_content_key = None
_json_metadata = {} # Callers are encouraged to set this.


class RestrictedRunAllowedForRestrictedCourseFactory(factory.django.DjangoModelFactory):
"""
Test factory for the `RestrictedRunAllowedForRestrictedCourse` model.
"""
class Meta:
model = RestrictedRunAllowedForRestrictedCourse

course = factory.SubFactory(RestrictedCourseMetadataFactory)
run = factory.SubFactory(ContentMetadataFactory)


class UserFactory(factory.django.DjangoModelFactory):
username = factory.Faker('user_name')
password = factory.PostGenerationMethodCall('set_password', USER_PASSWORD)
Expand Down
210 changes: 210 additions & 0 deletions enterprise_catalog/apps/catalog/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,213 @@ def test_restricted_runs_are_none(self, restricted_runs_dict):

self.assertIsNone(catalog_query.restricted_runs_allowed)
self.assertIsNone(catalog.restricted_runs_allowed)


@ddt.ddt
class TestRestrictedRunsModels(TestCase):
"""
Tests for the following models pertaining to the "restricted runs" feature:
* RestrictedCourseMetadata
* RestrictedRunAllowedForRestrictedCourse
"""

@ddt.data(
# Skip creating any content metadata at all. The result should be empty.
{
'create_catalog_query': {
'11111111-1111-1111-1111-111111111111': {
'content_filter': {},
},
},
'expected_json_metadata': [],
'expected_json_metadata_with_restricted': [],
},
# Create a simple course and run, but it is not part of the catalog. The result should be empty.
{
'create_catalog_query': {
'11111111-1111-1111-1111-111111111111': {
'content_filter': {},
},
'22222222-2222-2222-2222-222222222222': {
'content_filter': {},
},
},
'create_content_metadata': {
'edX+course': {
'create_runs': {
'course-v1:edX+course+run1': {'is_restricted': False},
},
'json_metadata': {'foobar': 'base metadata'},
'associate_with_catalog_query': '22222222-2222-2222-2222-222222222222', # different!
},
},
'expected_json_metadata': [],
'expected_json_metadata_with_restricted': [],
},
# Create a simple course and run, associated with the catalog.
{
'create_catalog_query': {
'11111111-1111-1111-1111-111111111111': {
'content_filter': {},
},
},
'create_content_metadata': {
'edX+course': {
'create_runs': {
'course-v1:edX+course+run1': {'is_restricted': False},
},
'json_metadata': {'foobar': 'base metadata'},
'associate_with_catalog_query': '11111111-1111-1111-1111-111111111111',
},
},
'expected_json_metadata': [
{'foobar': 'base metadata'},
],
'expected_json_metadata_with_restricted': [
{'foobar': 'base metadata'},
],
},
# Create a course with a restricted run, but the run is not allowed by the main CatalogQuery.
{
'create_catalog_query': {
'11111111-1111-1111-1111-111111111111': {
'content_filter': {
'restricted_runs_allowed': {
'course:edX+course': [
'course-v1:edX+course+run2',
],
},
},
},
'22222222-2222-2222-2222-222222222222': {
'content_filter': {},
},
},
'create_content_metadata': {
'edX+course': {
'create_runs': {
'course-v1:edX+course+run1': {'is_restricted': False},
'course-v1:edX+course+run2': {'is_restricted': True},
},
'json_metadata': {'foobar': 'base metadata'},
'associate_with_catalog_query': '11111111-1111-1111-1111-111111111111',
},
},
'create_restricted_courses': {
0: {
'content_key': 'edX+course',
'catalog_query': '22222222-2222-2222-2222-222222222222',
'json_metadata': {'foobar': 'override metadata'},
},
},
'create_restricted_run_allowed_for_restricted_course': [
{'course': 0, 'run': 'course-v1:edX+course+run2'},
],
'expected_json_metadata': [
{'foobar': 'base metadata'},
],
'expected_json_metadata_with_restricted': [
{'foobar': 'base metadata'},
],
},
# Create a course with a restricted run, and the run is allowed by the main CatalogQuery.
{
'create_catalog_query': {
'11111111-1111-1111-1111-111111111111': {
'content_filter': {
'restricted_runs_allowed': {
'course:edX+course': [
'course-v1:edX+course+run2',
],
},
},
},
},
'create_content_metadata': {
'edX+course': {
'create_runs': {
'course-v1:edX+course+run1': {'is_restricted': False},
'course-v1:edX+course+run2': {'is_restricted': True},
},
'json_metadata': {'foobar': 'base metadata'},
'associate_with_catalog_query': '11111111-1111-1111-1111-111111111111',
},
},
'create_restricted_courses': {
0: {
'content_key': 'edX+course',
'catalog_query': '11111111-1111-1111-1111-111111111111',
'json_metadata': {'foobar': 'override metadata'},
},
},
'create_restricted_run_allowed_for_restricted_course': [
{'course': 0, 'run': 'course-v1:edX+course+run2'},
],
'expected_json_metadata': [
{'foobar': 'base metadata'},
],
'expected_json_metadata_with_restricted': [
{'foobar': 'base metadata'},
],
},
)
@ddt.unpack
def test_catalog_content_metadata_with_restricted_runs(
self,
create_catalog_query,
create_content_metadata=None,
create_restricted_courses=None,
create_restricted_run_allowed_for_restricted_course=None,
expected_json_metadata=None,
expected_json_metadata_with_restricted=None,
):
"""
"""
catalog_queries = {
cq_uuid: factories.CatalogQueryFactory(
uuid=cq_uuid,
content_filter=cq_info['content_filter'] | {'force_unique': cq_uuid},
) for cq_uuid, cq_info in create_catalog_query.items()
}
content_metadata = {}
create_content_metadata = create_content_metadata or {}
for course_key, course_info in create_content_metadata.items():
course = factories.ContentMetadataFactory(
content_key=course_key,
content_type=COURSE,
_json_metadata=course_info['json_metadata'],
)
content_metadata.update({course_key: course})
if cq_uuid := course_info['associate_with_catalog_query']:
course.catalog_queries.set([catalog_queries[cq_uuid]])
for run_key, run_info in course_info['create_runs'].items():
run = factories.ContentMetadataFactory(
content_key=run_key,
content_type=COURSE_RUN,
)
if run_info['is_restricted']:
# pylint: disable=protected-access
run._json_metadata.update({'restriction_type': 'custom-b2b-enterprise'})
run.save()
content_metadata.update({run_key: run})
restricted_courses = {
id: factories.RestrictedCourseMetadataFactory(
id=id,
content_key=restricted_course_info['content_key'],
_json_metadata=restricted_course_info['json_metadata'],
) for id, restricted_course_info in create_restricted_courses.items()
} if create_restricted_courses else {}
for mapping_info in create_restricted_run_allowed_for_restricted_course or []:
factories.RestrictedRunAllowedForRestrictedCourseFactory(
course=restricted_courses[mapping_info['course']],
run=content_metadata[mapping_info['run']],
)
catalog = factories.EnterpriseCatalogFactory(
catalog_query=catalog_queries['11111111-1111-1111-1111-111111111111'],
)
expected_json_metadata = expected_json_metadata or []
expected_json_metadata_with_restricted = expected_json_metadata_with_restricted or []
actual_json_metadata = [m.json_metadata for m in catalog.content_metadata]
actual_json_metadata_with_restricted = [m.json_metadata for m in catalog.content_metadata_with_restricted]
assert actual_json_metadata == expected_json_metadata
assert actual_json_metadata_with_restricted == expected_json_metadata_with_restricted

0 comments on commit 46d70a8

Please sign in to comment.