Skip to content

Commit 896ca99

Browse files
authored
chore: calling other djangoapps from API instead of model (#36448)
switching from calling other djangoapps via direct model access to calling from API. This included adding an API in the Student app. FIXES: APER-3972
1 parent 3834f20 commit 896ca99

4 files changed

Lines changed: 85 additions & 36 deletions

File tree

common/djangoapps/student/api.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66

7+
from typing import TYPE_CHECKING
78
import logging
89

910
from django.contrib.auth import get_user_model
@@ -32,6 +33,10 @@
3233
)
3334
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
3435

36+
if TYPE_CHECKING:
37+
from django.contrib.auth.models import AnonymousUser, User # pylint: disable=imported-auth-user
38+
from django.db.models.query import QuerySet
39+
3540

3641
# This is done so that if these strings change within the app, we can keep exported constants the same
3742
ENROLLED_TO_ENROLLED = _ENROLLED_TO_ENROLLED
@@ -92,13 +97,7 @@ def create_manual_enrollment_audit(
9297
else:
9398
enrollment = None
9499

95-
_create_manual_enrollment_audit(
96-
enrolled_by,
97-
user_email,
98-
transition_state,
99-
reason,
100-
enrollment
101-
)
100+
_create_manual_enrollment_audit(enrolled_by, user_email, transition_state, reason, enrollment)
102101

103102

104103
def get_access_role_by_role_name(role_name):
@@ -132,7 +131,31 @@ def is_user_staff_or_instructor_in_course(user, course_key):
132131
course_key = CourseKey.from_string(course_key)
133132

134133
return (
135-
GlobalStaff().has_user(user) or
136-
CourseStaffRole(course_key).has_user(user) or
137-
CourseInstructorRole(course_key).has_user(user)
134+
GlobalStaff().has_user(user)
135+
or CourseStaffRole(course_key).has_user(user)
136+
or CourseInstructorRole(course_key).has_user(user)
138137
)
138+
139+
140+
def get_course_enrollments(
141+
user: "AnonymousUser | User",
142+
is_filtered: bool = False,
143+
course_ids: list[str | None] | None = None,
144+
) -> "QuerySet[CourseEnrollment]":
145+
"""
146+
Return enrollments for a user, potentially filtered by course_id.
147+
148+
Because an empty `course_ids` value is a meaningful filter, the easiest way to verify
149+
that the list should be filtered intentionally is to specify `is_filtered`.
150+
151+
Arguments:
152+
153+
* is_filtered (bool): whether or not the list is filtered
154+
* course_ids (list): a list of course IDs to filter by.
155+
"""
156+
course_enrollments = CourseEnrollment.enrollments_for_user(user).select_related("course")
157+
158+
if is_filtered:
159+
course_enrollments = course_enrollments.filter(course_id__in=course_ids)
160+
161+
return course_enrollments

common/djangoapps/student/tests/test_api.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
"""
22
Test Student api.py
33
"""
4+
45
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
56
from xmodule.modulestore.tests.factories import CourseFactory
67

7-
from common.djangoapps.student.api import is_user_enrolled_in_course, is_user_staff_or_instructor_in_course
8+
from common.djangoapps.student.api import (
9+
is_user_enrolled_in_course,
10+
is_user_staff_or_instructor_in_course,
11+
get_course_enrollments,
12+
)
13+
from common.djangoapps.student.models import CourseEnrollment
814
from common.djangoapps.student.tests.factories import (
915
CourseEnrollmentFactory,
1016
GlobalStaffFactory,
@@ -33,10 +39,7 @@ def test_is_user_enrolled_in_course(self):
3339
"""
3440
Verify the correct value is returned when a learner is actively enrolled in a course-run.
3541
"""
36-
CourseEnrollmentFactory.create(
37-
user_id=self.user.id,
38-
course_id=self.course.id
39-
)
42+
CourseEnrollmentFactory.create(user_id=self.user.id, course_id=self.course.id)
4043

4144
result = is_user_enrolled_in_course(self.user, self.course_run_key)
4245
assert result
@@ -45,11 +48,7 @@ def test_is_user_enrolled_in_course_not_active(self):
4548
"""
4649
Verify the correct value is returned when a learner is not actively enrolled in a course-run.
4750
"""
48-
CourseEnrollmentFactory.create(
49-
user_id=self.user.id,
50-
course_id=self.course.id,
51-
is_active=False
52-
)
51+
CourseEnrollmentFactory.create(user_id=self.user.id, course_id=self.course.id, is_active=False)
5352

5453
result = is_user_enrolled_in_course(self.user, self.course_run_key)
5554
assert not result
@@ -79,3 +78,25 @@ def test_is_user_staff_or_instructor(self):
7978
assert is_user_staff_or_instructor_in_course(instructor, self.course_run_key)
8079
assert not is_user_staff_or_instructor_in_course(self.user, self.course_run_key)
8180
assert not is_user_staff_or_instructor_in_course(instructor_different_course, self.course_run_key)
81+
82+
def test_get_course_enrollments(self):
83+
"""Verify all enrollments can be retrieved"""
84+
course_2 = CourseFactory.create()
85+
CourseEnrollmentFactory.create(user_id=self.user.id, course_id=self.course.id)
86+
CourseEnrollmentFactory.create(user_id=self.user.id, course_id=course_2.id)
87+
expected = CourseEnrollment.objects.all()
88+
89+
result = get_course_enrollments(self.user)
90+
91+
self.assertQuerySetEqual(expected, result)
92+
93+
def test_get_filtered_course_enrollments(self):
94+
"""Verify a filtered subset of enrollments can be retrieved"""
95+
course_2 = CourseFactory.create()
96+
CourseEnrollmentFactory.create(user_id=self.user.id, course_id=self.course.id)
97+
ce_2 = CourseEnrollmentFactory.create(user_id=self.user.id, course_id=course_2.id)
98+
expected = CourseEnrollment.objects.filter(id=ce_2.id)
99+
100+
result = get_course_enrollments(self.user, True, course_ids=[course_2.id])
101+
102+
self.assertQuerySetEqual(expected, result)

openedx/core/djangoapps/programs/rest_api/v1/tests/test_views.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""
2-
Unit tests for Learner Dashboard REST APIs and Views
2+
Unit tests for Programs REST APIs and Views
33
"""
44

55
from unittest import mock
66
from uuid import uuid4
77

88
from django.core.cache import cache
9+
from django.test.utils import override_settings
910
from django.urls import reverse_lazy
1011
from enterprise.models import EnterpriseCourseEnrollment
1112

@@ -31,6 +32,7 @@
3132
with_site_configuration,
3233
)
3334
from openedx.core.djangolib.testing.utils import skip_unless_lms
35+
from openedx.features.enterprise_support.api import enterprise_is_enabled
3436
from openedx.features.enterprise_support.tests.factories import (
3537
EnterpriseCourseEnrollmentFactory,
3638
EnterpriseCustomerFactory,
@@ -192,6 +194,8 @@ def setUp(self):
192194
)
193195

194196
@with_site_configuration(configuration={"COURSE_CATALOG_API_URL": "foo"})
197+
@override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True))
198+
@enterprise_is_enabled()
195199
def test_program_list(self):
196200
"""
197201
Verify API returns proper response.
@@ -221,6 +225,8 @@ def test_program_list(self):
221225
}
222226

223227
@with_site_configuration(configuration={"COURSE_CATALOG_API_URL": "foo"})
228+
@override_settings(FEATURES=dict(ENABLE_ENTERPRISE_INTEGRATION=True))
229+
@enterprise_is_enabled()
224230
def test_program_empty_list_if_no_enterprise_enrollments(self):
225231
"""
226232
Verify API returns empty response if no enterprise enrollments exists for a learner.

openedx/core/djangoapps/programs/rest_api/v1/views.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@
33
from typing import Any, TYPE_CHECKING
44
import logging
55

6-
from enterprise.models import EnterpriseCourseEnrollment
6+
from django.db.models.query import EmptyQuerySet
77
from rest_framework.permissions import IsAuthenticated
88
from rest_framework.response import Response
99
from rest_framework.views import APIView
1010

11-
from common.djangoapps.student.models import CourseEnrollment
11+
from common.djangoapps.student.api import get_course_enrollments
1212
from openedx.core.djangoapps.programs.utils import (
1313
ProgramProgressMeter,
1414
get_certificates,
1515
get_industry_and_credit_pathways,
1616
get_program_and_course_data,
1717
get_program_urls,
1818
)
19+
from openedx.features.enterprise_support.api import get_enterprise_course_enrollments, enterprise_is_enabled
1920

2021
if TYPE_CHECKING:
2122
from django.http import HttpRequest, HttpResponse
2223
from django.contrib.auth.models import AnonymousUser, User # pylint: disable=imported-auth-user
2324
from django.contrib.sites.models import Site
25+
from django.db.models.query import QuerySet
26+
from common.djangoapps.student.models import CourseEnrollment
2427

2528
logger = logging.getLogger(__name__)
2629

@@ -86,7 +89,7 @@ def get(self, request: "HttpRequest", enterprise_uuid: str) -> "HttpResponse":
8689
"""
8790
user: "AnonymousUser | User" = request.user
8891

89-
enrollments = self._get_enterprise_course_enrollments(enterprise_uuid, user)
92+
enrollments = list(self._get_enterprise_course_enrollments(enterprise_uuid, user))
9093
# return empty reponse if no enterprise enrollments exists for a user
9194
if not enrollments:
9295
return Response([])
@@ -170,26 +173,22 @@ def transform_authoring_organizations(authoring_organizations) -> list[dict[str,
170173

171174
return programs
172175

176+
@enterprise_is_enabled(otherwise=EmptyQuerySet)
173177
def _get_enterprise_course_enrollments(
174178
self, enterprise_uuid: str, user: "AnonymousUser | User"
175-
) -> list[CourseEnrollment]:
179+
) -> "QuerySet[CourseEnrollment]":
176180
"""
177181
Return only enterprise enrollments for a user.
178182
"""
179-
enterprise_enrollment_course_ids = list(
180-
EnterpriseCourseEnrollment.objects.filter(
181-
enterprise_customer_user__user_id=user.id,
182-
enterprise_customer_user__enterprise_customer__uuid=enterprise_uuid,
183-
).values_list("course_id", flat=True)
183+
enterprise_enrollment_course_ids = (
184+
get_enterprise_course_enrollments(user)
185+
.filter(enterprise_customer_user__enterprise_customer__uuid=enterprise_uuid)
186+
.values_list("course_id", flat=True)
184187
)
185188

186-
course_enrollments = (
187-
CourseEnrollment.enrollments_for_user(user)
188-
.filter(course_id__in=enterprise_enrollment_course_ids)
189-
.select_related("course")
190-
)
189+
course_enrollments = get_course_enrollments(user, True, list(enterprise_enrollment_course_ids))
191190

192-
return list(course_enrollments)
191+
return course_enrollments
193192

194193

195194
class ProgramProgressDetailView(APIView):

0 commit comments

Comments
 (0)