chore: added waffle for allowing only verified users to create post (#37051)

This commit is contained in:
Muhammad Adeel Tajamul
2025-07-22 14:59:54 +05:00
committed by GitHub
parent e1a801a700
commit 01beca4ebb
4 changed files with 131 additions and 6 deletions

View File

@@ -35,7 +35,7 @@ from common.djangoapps.student.roles import (
from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE, ONLY_VERIFIED_USERS_CAN_POST
from lms.djangoapps.discussion.views import is_privileged_user
from openedx.core.djangoapps.discussions.models import (
DiscussionsConfiguration,
@@ -385,7 +385,7 @@ def get_course(request, course_key, check_tab=True):
'site_key': settings.RECAPTCHA_SITE_KEY,
},
"is_email_verified": request.user.is_active,
"only_verified_users_can_post": False,
"only_verified_users_can_post": ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key),
}

View File

@@ -20,7 +20,7 @@ from pytz import UTC
from rest_framework import status
from rest_framework.test import APIClient, APITestCase
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE
from lms.djangoapps.discussion.toggles import ENABLE_DISCUSSIONS_MFE, ONLY_VERIFIED_USERS_CAN_POST
from lms.djangoapps.discussion.rest_api.utils import get_usernames_from_search_string
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -1064,6 +1064,7 @@ class CourseTopicsViewV3Test(DiscussionAPIViewTestMixin, CommentsServiceMockMixi
assert vertical_keys == expected_non_courseware_keys
@ddt.ddt
@httpretty.activate
@disable_signal(api, 'thread_created')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -1139,6 +1140,41 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
response_data = json.loads(response.content.decode('utf-8'))
assert response_data == expected_response_data
@ddt.data(
(False, False, status.HTTP_200_OK),
(False, True, status.HTTP_400_BAD_REQUEST),
(True, False, status.HTTP_200_OK),
(True, True, status.HTTP_200_OK),
)
@ddt.unpack
def test_creation_for_non_verified_user(self, email_verified, only_verified_user_can_post, response_status):
"""
Tests posts cannot be created if ONLY_VERIFIED_USERS_CAN_POST is enabled and user email is unverified.
"""
with override_waffle_flag(ONLY_VERIFIED_USERS_CAN_POST, only_verified_user_can_post):
self.user.is_active = email_verified
self.user.save()
self.register_get_user_response(self.user)
cs_thread = make_minimal_cs_thread({
"id": "test_thread",
"username": self.user.username,
"read": True,
})
self.register_post_thread_response(cs_thread)
request_data = {
"course_id": str(self.course.id),
"topic_id": "test_topic",
"type": "discussion",
"title": "Test Title",
"raw_body": "# Test \n This is a very long body but will not be truncated for the preview.",
}
response = self.client.post(
self.url,
json.dumps(request_data),
content_type="application/json"
)
assert response.status_code == response_status
@httpretty.activate
@disable_signal(api, 'thread_deleted')
@@ -2019,6 +2055,7 @@ class CommentViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
assert response.status_code == 404
@ddt.ddt
@httpretty.activate
@disable_signal(api, 'comment_created')
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -2134,6 +2171,69 @@ class CommentViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
)
assert response.status_code == 403
@ddt.data(
(False, False, status.HTTP_200_OK),
(False, True, status.HTTP_400_BAD_REQUEST),
(True, False, status.HTTP_200_OK),
(True, True, status.HTTP_200_OK),
)
@ddt.unpack
def test_creation_for_non_verified_user(self, email_verified, only_verified_user_can_post, response_status):
"""
Tests comments/replies cannot be created if ONLY_VERIFIED_USERS_CAN_POST is enabled and
user email is unverified.
"""
with override_waffle_flag(ONLY_VERIFIED_USERS_CAN_POST, only_verified_user_can_post):
self.user.is_active = email_verified
self.user.save()
self.register_get_user_response(self.user)
self.register_thread()
self.register_comment()
request_data = {
"thread_id": "test_thread",
"raw_body": "Test body",
}
expected_response_data = {
"id": "test_comment",
"thread_id": "test_thread",
"parent_id": None,
"author": self.user.username,
"author_label": None,
"created_at": "1970-01-01T00:00:00Z",
"updated_at": "1970-01-01T00:00:00Z",
"raw_body": "Test body",
"rendered_body": "<p>Test body</p>",
"endorsed": False,
"endorsed_by": None,
"endorsed_by_label": None,
"endorsed_at": None,
"abuse_flagged": False,
"abuse_flagged_any_user": None,
"voted": False,
"vote_count": 0,
"children": [],
"editable_fields": ["abuse_flagged", "anonymous", "raw_body"],
"child_count": 0,
"can_delete": True,
"anonymous": False,
"anonymous_to_peers": False,
"last_edit": None,
"edit_by_label": None,
"profile_image": {
"has_image": False,
"image_url_full": "http://testserver/static/default_500.png",
"image_url_large": "http://testserver/static/default_120.png",
"image_url_medium": "http://testserver/static/default_50.png",
"image_url_small": "http://testserver/static/default_30.png",
},
}
response = self.client.post(
self.url,
json.dumps(request_data),
content_type="application/json"
)
assert response.status_code == response_status
@httpretty.activate
@mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})

View File

@@ -29,6 +29,7 @@ from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.course_goals.models import UserActivity
from lms.djangoapps.discussion.rest_api.permissions import IsAllowedToBulkDelete
from lms.djangoapps.discussion.rest_api.tasks import delete_course_post_for_user
from lms.djangoapps.discussion.toggles import ONLY_VERIFIED_USERS_CAN_POST
from lms.djangoapps.discussion.django_comment_client import settings as cc_settings
from lms.djangoapps.discussion.django_comment_client.utils import get_group_id_for_comments_service
from lms.djangoapps.instructor.access import update_forum_role
@@ -671,13 +672,19 @@ class ThreadViewSet(DeveloperErrorViewMixin, ViewSet):
"""
if not request.data.get("course_id"):
raise ValidationError({"course_id": ["This field is required."]})
if is_captcha_enabled(CourseKey.from_string(request.data.get("course_id"))):
course_key_str = request.data.get("course_id")
course_key = CourseKey.from_string(course_key_str)
if is_captcha_enabled(course_key):
captcha_token = request.data.get('captcha_token')
if not captcha_token:
raise ValidationError({'captcha_token': 'This field is required.'})
if not verify_recaptcha_token(captcha_token):
return Response({'error': 'CAPTCHA verification failed.'}, status=400)
if ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key) and not request.user.is_active:
raise ValidationError({"detail": "Only verified users can post in discussions."})
data = request.data.copy()
data.pop('captcha_token', None)
return Response(create_thread(request, data))
@@ -1037,14 +1044,20 @@ class CommentViewSet(DeveloperErrorViewMixin, ViewSet):
"""
if not request.data.get("thread_id"):
raise ValidationError({"thread_id": ["This field is required."]})
course_id = get_course_id_from_thread_id(request.data["thread_id"])
if is_captcha_enabled(CourseKey.from_string(course_id)):
course_key_str = get_course_id_from_thread_id(request.data["thread_id"])
course_key = CourseKey.from_string(course_key_str)
if is_captcha_enabled(course_key):
captcha_token = request.data.get('captcha_token')
if not captcha_token:
raise ValidationError({'captcha_token': 'This field is required.'})
if not verify_recaptcha_token(captcha_token):
return Response({'error': 'CAPTCHA verification failed.'}, status=400)
if ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key) and not request.user.is_active:
raise ValidationError({"detail": "Only verified users can post in discussions."})
data = request.data.copy()
data.pop('captcha_token', None)
return Response(create_comment(request, data))

View File

@@ -15,3 +15,15 @@ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
ENABLE_DISCUSSIONS_MFE = CourseWaffleFlag(
f"{WAFFLE_FLAG_NAMESPACE}.enable_discussions_mfe", __name__
)
# .. toggle_name: discussions.only_verified_users_can_post
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to allow only verified users to post in discussions
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2025-22-07
# .. toggle_target_removal_date: 2026-04-01
ONLY_VERIFIED_USERS_CAN_POST = CourseWaffleFlag(
f"{WAFFLE_FLAG_NAMESPACE}.only_verified_users_can_post", __name__
)