Skip to content

Commit 73fa154

Browse files
refactor: logging added with some code refactoring
1 parent 0c0a1a3 commit 73fa154

File tree

6 files changed

+127
-35
lines changed

6 files changed

+127
-35
lines changed

cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def test_dynamic_course_app_settings_mapping(self):
223223
Test that the course app settings mapping is dynamically discovered from plugins.
224224
"""
225225
# Get the dynamic mapping
226-
mapping = CourseAppsPluginManager.get_course_app_settings_mapping()
226+
mapping = CourseAppsPluginManager.get_course_app_settings_mapping(self.course.id)
227227

228228
# Verify that calculator and edxnotes are in the mapping
229229
assert "show_calculator" in mapping

cms/djangoapps/contentstore/rest_api/v0/views/advanced_settings.py

Lines changed: 110 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" API Views for course advanced settings """
22

3+
import logging
34
from django import forms
45
import edx_api_doc_tools as apidocs
56
from opaque_keys.edx.keys import CourseKey
@@ -18,6 +19,8 @@
1819
from ..serializers import CourseAdvancedSettingsSerializer
1920
from ....views.course import update_course_advanced_settings
2021

22+
log = logging.getLogger(__name__)
23+
2124

2225
@view_auth_classes(is_authenticated=True)
2326
class AdvancedCourseSettingsView(DeveloperErrorViewMixin, APIView):
@@ -190,26 +193,114 @@ def patch(self, request: Request, course_id: str):
190193
if not has_studio_write_access(request.user, course_key):
191194
self.permission_denied(request)
192195

193-
course_app_settings_map = CourseAppsPluginManager.get_course_app_settings_mapping()
194-
for setting in course_app_settings_map:
195-
if setting_to_update := request.data.get(setting):
196-
course_app_enabled = setting_to_update.get("value", None)
197-
if course_app_enabled is not None:
198-
try:
199-
set_course_app_status(
200-
course_key=course_key,
201-
app_id=course_app_settings_map[setting],
202-
enabled=course_app_enabled,
203-
request=request,
204-
)
205-
# enabling/disabling the course app setting also updates
206-
# the advanced settings, so we remove it from the request data
207-
request.data.pop(setting)
208-
except ValidationError:
209-
# Failed to update the course app status,
210-
# we ignore and let the normal flow handle updates
211-
pass
196+
# Process course app settings that have corresponding advanced settings
197+
self._process_course_app_settings(request, course_key)
212198

213199
course_block = modulestore().get_course(course_key)
214200
updated_data = update_course_advanced_settings(course_block, request.data, request.user)
215201
return Response(updated_data)
202+
203+
def _process_course_app_settings(self, request: Request, course_key: CourseKey) -> None:
204+
"""
205+
Process course app settings that have corresponding advanced settings.
206+
207+
Updates course app status for settings that are managed by course apps,
208+
and removes them from the request data to avoid duplicate processing.
209+
210+
Args:
211+
request: The HTTP request containing settings to update
212+
course_key: The course key for the course being updated
213+
"""
214+
course_app_settings_map = CourseAppsPluginManager.get_course_app_settings_mapping(course_key)
215+
216+
if not course_app_settings_map:
217+
log.debug("No course app settings mapping found for course: %s", course_key)
218+
return
219+
220+
log.debug(
221+
"Processing course app settings: course_key=%s, user=%s, "
222+
"available_app_settings=%s, request_settings=%s",
223+
course_key, request.user.username,
224+
list(course_app_settings_map.keys()), list(request.data.keys())
225+
)
226+
227+
settings_processed = 0
228+
settings_failed = 0
229+
230+
for setting_name in course_app_settings_map:
231+
setting_data = request.data.get(setting_name)
232+
if not setting_data:
233+
continue
234+
235+
new_value = setting_data.get("value")
236+
if new_value is None:
237+
log.debug("Skipping setting %s - no value provided", setting_name)
238+
continue
239+
240+
app_id = course_app_settings_map[setting_name]
241+
242+
if self._update_course_app_setting(
243+
request=request,
244+
course_key=course_key,
245+
setting_name=setting_name,
246+
app_id=app_id,
247+
enabled=new_value
248+
):
249+
settings_processed += 1
250+
# Remove from request data since it's been handled by course app
251+
request.data.pop(setting_name)
252+
else:
253+
settings_failed += 1
254+
255+
log.info(
256+
"Course app settings processing complete: course_key=%s, "
257+
"processed=%d, failed=%d (falling back to advanced settings)",
258+
course_key, settings_processed, settings_failed
259+
)
260+
261+
def _update_course_app_setting(
262+
self, request: Request, course_key: CourseKey,
263+
setting_name: str, app_id: str, enabled: bool
264+
) -> bool:
265+
"""
266+
Update a single course app setting.
267+
268+
Args:
269+
request: The HTTP request
270+
course_key: The course key
271+
setting_name: Name of the advanced setting
272+
app_id: ID of the course app
273+
enabled: Whether to enable or disable the app
274+
275+
Returns:
276+
bool: True if update was successful, False if it failed
277+
"""
278+
try:
279+
log.debug(
280+
"Attempting course app update: course_key=%s, app_id=%s, "
281+
"setting=%s, enabled=%s, user=%s",
282+
course_key, app_id, setting_name, enabled, request.user.username
283+
)
284+
285+
set_course_app_status(
286+
course_key=course_key,
287+
app_id=app_id,
288+
enabled=enabled,
289+
user=request.user,
290+
)
291+
292+
log.info(
293+
"Successfully updated course app via advanced settings: "
294+
"course_key=%s, app_id=%s, setting=%s, enabled=%s, user=%s",
295+
course_key, app_id, setting_name, enabled, request.user.username
296+
)
297+
return True
298+
299+
except ValidationError as e:
300+
log.warning(
301+
"Course app update failed with validation error, "
302+
"will fallback to advanced settings flow: "
303+
"course_key=%s, app_id=%s, setting=%s, enabled=%s, user=%s, error=%s",
304+
course_key, app_id, setting_name, enabled, request.user.username, str(e)
305+
)
306+
return False

openedx/core/djangoapps/course_apps/api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ def set_course_app_enabled(course_key: CourseKey, app_id: str, enabled: bool, us
6666
return enabled
6767

6868

69-
def set_course_app_status(course_key, app_id, enabled, request):
69+
def set_course_app_status(course_key: CourseKey, app_id: str, enabled: bool, user: User) -> bool:
7070
"""
7171
Enable or disable a course app for a given course.
7272
7373
Args:
7474
course_key (CourseKey): The course key for the course to update.
7575
app_id (str): The app ID of the course app to enable/disable.
7676
enabled (bool): The desired enabled status.
77-
request (HttpRequest): The HTTP request object
77+
user (User): The user performing the operation.
7878
7979
Returns:
8080
bool: The final enabled/disabled status of the app.
@@ -86,4 +86,4 @@ def set_course_app_status(course_key, app_id, enabled, request):
8686
if not course_app or not course_app.is_available(course_key):
8787
raise ValidationError({"id": "Invalid app ID"})
8888

89-
return set_course_app_enabled(course_key=course_key, app_id=app_id, enabled=enabled, user=request.user)
89+
return set_course_app_enabled(course_key=course_key, app_id=app_id, enabled=enabled, user=user)

openedx/core/djangoapps/course_apps/plugins.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CourseApp(ABC):
3030
}
3131
# Advanced setting field name that corresponds to this app (optional)
3232
# Should be a string representing the field name in course advanced settings
33-
advanced_setting_field = None
33+
advanced_setting_field: Optional[str] = None
3434

3535
@classmethod
3636
@abstractmethod
@@ -120,20 +120,23 @@ def get_apps_available_for_course(cls, course_key: CourseKey) -> Iterator[Course
120120
yield plugin
121121

122122
@classmethod
123-
def get_course_app_settings_mapping(cls) -> Dict[str, str]:
123+
def get_course_app_settings_mapping(cls, course_key: CourseKey) -> Dict[str, str]:
124124
"""
125125
Returns a mapping of advanced setting field names to course app IDs.
126126
127127
This method dynamically discovers all course app plugins and builds a mapping
128128
based on their advanced_setting_field attributes.
129129
130+
Args:
131+
course_key (CourseKey): The course key for which the mapping is to be generated.
132+
130133
Returns:
131134
Dict[str, str]: A dictionary mapping advanced setting field names to app IDs.
132135
"""
133136
settings_mapping = {}
134137

135-
for plugin in super().get_available_plugins().values():
136-
if hasattr(plugin, 'advanced_setting_field') and plugin.advanced_setting_field:
138+
for plugin in cls.get_apps_available_for_course(course_key):
139+
if getattr(plugin, 'advanced_setting_field', None):
137140
settings_mapping[plugin.advanced_setting_field] = plugin.app_id
138141

139142
return settings_mapping

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,7 @@ def patch(self, request: Request, course_id: str):
191191
raise ValidationError({"id": "App id is missing"})
192192
if enabled is None:
193193
raise ValidationError({"enabled": "Must provide value for `enabled` field."})
194-
195-
is_enabled = set_course_app_status(course_key=course_key, app_id=app_id, enabled=enabled, request=request)
194+
is_enabled = set_course_app_status(course_key, app_id, enabled, request.user)
196195
course_app = CourseAppsPluginManager.get_plugin(app_id)
197196
serializer = CourseAppSerializer(
198197
course_app,

openedx/core/djangoapps/course_apps/tests/test_dynamic_settings_mapping.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
"""
44
from unittest.mock import patch
55

6-
from django.test import TestCase
7-
6+
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
87
from openedx.core.djangoapps.course_apps.plugins import CourseAppsPluginManager, CourseApp
98

109

@@ -57,7 +56,7 @@ def get_allowed_operations(cls, course_key, user=None):
5756
return {"enable": True, "configure": True}
5857

5958

60-
class DynamicSettingsMappingTest(TestCase):
59+
class DynamicSettingsMappingTest(CourseTestCase):
6160
"""Test dynamic course app settings mapping functionality."""
6261

6362
def test_app_with_advanced_setting_mapping(self):
@@ -67,7 +66,7 @@ def test_app_with_advanced_setting_mapping(self):
6766
}
6867

6968
with patch('edx_django_utils.plugins.PluginManager.get_available_plugins', return_value=mock_plugins):
70-
mapping = CourseAppsPluginManager.get_course_app_settings_mapping()
69+
mapping = CourseAppsPluginManager.get_course_app_settings_mapping(self.course.id)
7170

7271
assert "settings_app_field" in mapping
7372
assert mapping["settings_app_field"] == "settings_app"
@@ -79,7 +78,7 @@ def test_no_advanced_setting_fields(self):
7978
}
8079

8180
with patch('edx_django_utils.plugins.PluginManager.get_available_plugins', return_value=mock_plugins):
82-
mapping = CourseAppsPluginManager.get_course_app_settings_mapping()
81+
mapping = CourseAppsPluginManager.get_course_app_settings_mapping(self.course.id)
8382

8483
assert len(mapping) == 0
8584

@@ -91,7 +90,7 @@ def test_mixed_apps_mapping(self):
9190
}
9291

9392
with patch('edx_django_utils.plugins.PluginManager.get_available_plugins', return_value=mock_plugins):
94-
mapping = CourseAppsPluginManager.get_course_app_settings_mapping()
93+
mapping = CourseAppsPluginManager.get_course_app_settings_mapping(self.course.id)
9594

9695
# Should only include apps with advanced_setting_field
9796
assert len(mapping) == 1

0 commit comments

Comments
 (0)