feat: add Studio API for bulk enable/disable discussions for a course
Implemented Studio API for bulk enable/disable discussions for a course.
This commit is contained in:
committed by
Muhammad Faraz Maqsood
parent
854d04dd33
commit
0c493b6ec2
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Test the enable/disable discussions for all units API endpoint.
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.urls import reverse
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
|
||||
|
||||
class BulkEnableDisableDiscussionsTestCase(ModuleStoreTestCase):
|
||||
"""
|
||||
Test the enable/disable discussions for all units API endpoint.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = UserFactory(is_staff=True, is_superuser=True)
|
||||
self.user.set_password(self.user_password)
|
||||
self.user.save()
|
||||
|
||||
self.course_key = CourseKey.from_string("course-v1:edx+TestX+2025")
|
||||
|
||||
self.url = reverse('bulk_enable_disable_discussions', args=[str(self.course_key)])
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.user.username, password=self.user_password)
|
||||
|
||||
# Create a test course
|
||||
self.course = CourseFactory.create(
|
||||
org=self.course_key.org,
|
||||
course=self.course_key.course,
|
||||
run=self.course_key.run,
|
||||
default_store=ModuleStoreEnum.Type.split,
|
||||
display_name="EnableDisableDiscussionsTestCase Course",
|
||||
)
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
section = BlockFactory.create(
|
||||
parent=self.course,
|
||||
category='chapter',
|
||||
display_name="Generated Section",
|
||||
)
|
||||
sequence = BlockFactory.create(
|
||||
parent=section,
|
||||
category='sequential',
|
||||
display_name="Generated Sequence",
|
||||
)
|
||||
unit1 = BlockFactory.create(
|
||||
parent=sequence,
|
||||
category='vertical',
|
||||
display_name="Unit in Section1",
|
||||
discussion_enabled=True,
|
||||
)
|
||||
unit2 = BlockFactory.create(
|
||||
parent=sequence,
|
||||
category='vertical',
|
||||
display_name="Unit in Section2",
|
||||
discussion_enabled=True,
|
||||
)
|
||||
|
||||
def test_disable_discussions_for_all_units(self):
|
||||
"""
|
||||
Test that the API successfully disables discussions for all units.
|
||||
"""
|
||||
self.enable_disable_discussions_for_all_units(False)
|
||||
|
||||
def test_enable_discussions_for_all_units(self):
|
||||
"""
|
||||
Test that the API successfully enables discussions for all units.
|
||||
"""
|
||||
self.enable_disable_discussions_for_all_units(True)
|
||||
|
||||
def enable_disable_discussions_for_all_units(self, is_enabled):
|
||||
"""
|
||||
Test that the API successfully enables/disables discussions for all units.
|
||||
"""
|
||||
data = {
|
||||
"discussion_enabled": is_enabled
|
||||
}
|
||||
response = self.client.put(self.url, data=json.dumps(data), content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_data = response.json()
|
||||
print(response_data)
|
||||
self.assertEqual(response_data['updated_and_republished'], 0 if is_enabled else 2)
|
||||
|
||||
# Check that all verticals now have discussion_enabled set to the expected value
|
||||
with self.store.bulk_operations(self.course_key):
|
||||
verticals = self.store.get_items(self.course_key, qualifiers={'block_type': 'vertical'})
|
||||
for vertical in verticals:
|
||||
self.assertEqual(vertical.discussion_enabled, is_enabled)
|
||||
|
||||
def test_permission_denied_for_non_staff(self):
|
||||
"""
|
||||
Test that non-staff users are denied access to the API.
|
||||
"""
|
||||
# Create a non-staff user
|
||||
non_staff_user = UserFactory(is_staff=False, is_superuser=False)
|
||||
non_staff_user.set_password(self.user_password)
|
||||
non_staff_user.save()
|
||||
|
||||
# Create a new client for the non-staff user
|
||||
non_staff_client = AjaxEnabledTestClient()
|
||||
non_staff_client.login(username=non_staff_user.username, password=self.user_password)
|
||||
|
||||
response = non_staff_client.put(self.url, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_badrequest_for_empty_request_body(self):
|
||||
"""
|
||||
Test that the API returns a 400 for an empty request body.
|
||||
"""
|
||||
response = self.client.put(self.url, data=json.dumps({}), content_type='application/json')
|
||||
self.assertEqual(response.status_code, 400)
|
||||
@@ -135,7 +135,7 @@ __all__ = ['course_info_handler', 'course_handler', 'course_listing',
|
||||
'course_notifications_handler',
|
||||
'textbooks_list_handler', 'textbooks_detail_handler',
|
||||
'group_configurations_list_handler', 'group_configurations_detail_handler',
|
||||
'get_course_and_check_access']
|
||||
'get_course_and_check_access', 'bulk_enable_disable_discussions']
|
||||
|
||||
|
||||
class AccessListFallback(Exception):
|
||||
@@ -1710,6 +1710,62 @@ def group_configurations_detail_handler(request, course_key_string, group_config
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
@ensure_csrf_cookie
|
||||
@require_http_methods(["PUT"])
|
||||
def bulk_enable_disable_discussions(request, course_key_string):
|
||||
"""
|
||||
API endpoint to enable/disable discussions for all verticals in the course and republish them.
|
||||
|
||||
PUT
|
||||
json: enable/disable discussions for all units and republish
|
||||
"""
|
||||
try:
|
||||
# Validate the course key
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
except InvalidKeyError:
|
||||
return JsonResponseBadRequest({"error": "Invalid course key format"})
|
||||
|
||||
user = request.user
|
||||
|
||||
# check that logged in user has permissions to update this course
|
||||
if not has_studio_write_access(user, course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
if 'application/json' not in request.META.get('HTTP_ACCEPT', 'application/json'):
|
||||
return JsonResponseBadRequest({"error": "Only supports json requests"})
|
||||
|
||||
if 'discussion_enabled' not in request.json:
|
||||
return JsonResponseBadRequest({"error": "Missing 'discussion_enabled' field in request body"})
|
||||
discussion_enabled = request.json['discussion_enabled']
|
||||
log.info(
|
||||
"User %s is attempting to %s discussions for all verticals in course %s",
|
||||
user.username,
|
||||
"enable" if discussion_enabled else "disable",
|
||||
course_key
|
||||
)
|
||||
|
||||
if request.method == 'PUT':
|
||||
try:
|
||||
store = modulestore()
|
||||
changed = 0
|
||||
with store.bulk_operations(course_key):
|
||||
verticals = store.get_items(course_key, qualifiers={'block_type': 'vertical'})
|
||||
for vertical in verticals:
|
||||
if vertical.discussion_enabled != discussion_enabled:
|
||||
vertical.discussion_enabled = discussion_enabled
|
||||
store.update_item(vertical, user.id)
|
||||
|
||||
if store.has_published_version(vertical):
|
||||
store.publish(vertical.location, user.id)
|
||||
changed += 1
|
||||
return JsonResponse({"updated_and_republished": changed})
|
||||
except Exception as e: # lint-amnesty, pylint: disable=broad-except
|
||||
log.exception("Exception occurred while enabling/disabling discussion: %s", str(e))
|
||||
return JsonResponseBadRequest({"error": str(e)})
|
||||
|
||||
|
||||
def are_content_experiments_enabled(course):
|
||||
"""
|
||||
Returns True if content experiments have been enabled for the course.
|
||||
|
||||
@@ -201,6 +201,9 @@ urlpatterns = oauth2_urlpatterns + [
|
||||
path('accessibility', contentstore_views.accessibility, name='accessibility'),
|
||||
re_path(fr'api/youtube/courses/{COURSELIKE_KEY_PATTERN}/edx-video-ids$',
|
||||
contentstore_views.get_course_youtube_edx_videos_ids, name='youtube_edx_video_ids'),
|
||||
re_path(fr'^api/courses/{settings.COURSE_KEY_PATTERN}/bulk_enable_disable_discussions$',
|
||||
contentstore_views.bulk_enable_disable_discussions,
|
||||
name='bulk_enable_disable_discussions'),
|
||||
]
|
||||
|
||||
if not settings.DISABLE_DEPRECATED_SIGNIN_URL:
|
||||
|
||||
Reference in New Issue
Block a user