diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 05fc1705e83e..dd3baa2825d8 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -19,6 +19,7 @@ from xblock.runtime import KvsFieldData from openedx.core.djangoapps.video_config.services import VideoConfigService +from openedx.core.djangoapps.discussions.services import DiscussionConfigService from xmodule.contentstore.django import contentstore from xmodule.exceptions import NotFoundError as XModuleNotFoundError from xmodule.modulestore.django import XBlockI18nService, modulestore @@ -217,6 +218,7 @@ def _prepare_runtime_for_preview(request, block): "cache": CacheService(cache), 'replace_urls': ReplaceURLService, 'video_config': VideoConfigService(), + 'discussion_config': DiscussionConfigService(), } block.runtime.get_block_for_descriptor = partial(_load_preview_block, request) diff --git a/lms/djangoapps/courseware/block_render.py b/lms/djangoapps/courseware/block_render.py index b6e4145e2ecf..275f86e0b581 100644 --- a/lms/djangoapps/courseware/block_render.py +++ b/lms/djangoapps/courseware/block_render.py @@ -43,6 +43,7 @@ from lms.djangoapps.teams.services import TeamsService from openedx.core.djangoapps.video_config.services import VideoConfigService +from openedx.core.djangoapps.discussions.services import DiscussionConfigService from openedx.core.lib.xblock_services.call_to_action import CallToActionService from xmodule.contentstore.django import contentstore from xmodule.exceptions import NotFoundError as XModuleNotFoundError @@ -637,6 +638,7 @@ def inner_get_block(block: XBlock) -> XBlock | None: 'publish': EventPublishingService(user, course_id, track_function), 'enrollments': EnrollmentsService(), 'video_config': VideoConfigService(), + 'discussion_config': DiscussionConfigService(), } runtime.get_block_for_descriptor = inner_get_block diff --git a/lms/djangoapps/discussion/django_comment_client/base/views.py b/lms/djangoapps/discussion/django_comment_client/base/views.py index 14ce9c4b575a..e40ee4ef58bb 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/views.py +++ b/lms/djangoapps/discussion/django_comment_client/base/views.py @@ -25,6 +25,7 @@ import lms.djangoapps.discussion.django_comment_client.settings as cc_settings import openedx.core.djangoapps.django_comment_common.comment_client as cc +from openedx.core.djangoapps.django_comment_common.models import has_permission from common.djangoapps.student.roles import GlobalStaff from common.djangoapps.track import contexts from common.djangoapps.util.file import store_uploaded_file @@ -33,8 +34,7 @@ from lms.djangoapps.courseware.exceptions import CourseAccessRedirect from lms.djangoapps.discussion.django_comment_client.permissions import ( check_permissions_by_view, - get_team, - has_permission + get_team ) from lms.djangoapps.discussion.django_comment_client.utils import ( JsonError, diff --git a/lms/djangoapps/discussion/django_comment_client/permissions.py b/lms/djangoapps/discussion/django_comment_client/permissions.py index 2eeee32fe722..4801a461c608 100644 --- a/lms/djangoapps/discussion/django_comment_client/permissions.py +++ b/lms/djangoapps/discussion/django_comment_client/permissions.py @@ -12,26 +12,11 @@ from openedx.core.djangoapps.django_comment_common.comment_client import Thread from openedx.core.djangoapps.django_comment_common.models import ( CourseDiscussionSettings, - all_permissions_for_user_in_course + has_permission ) from openedx.core.lib.cache_utils import request_cached -def has_permission(user, permission, course_id=None): # lint-amnesty, pylint: disable=missing-function-docstring - assert isinstance(course_id, (type(None), CourseKey)) - request_cache_dict = DEFAULT_REQUEST_CACHE.data - cache_key = "django_comment_client.permissions.has_permission.all_permissions.{}.{}".format( - user.id, course_id - ) - if cache_key in request_cache_dict: - all_permissions = request_cache_dict[cache_key] - else: - all_permissions = all_permissions_for_user_in_course(user, course_id) - request_cache_dict[cache_key] = all_permissions - - return permission in all_permissions - - CONDITIONS = ['is_open', 'is_author', 'is_question_author', 'is_team_member_if_applicable'] diff --git a/lms/djangoapps/discussion/django_comment_client/utils.py b/lms/djangoapps/discussion/django_comment_client/utils.py index e26b748270e3..a0bb6b769183 100644 --- a/lms/djangoapps/discussion/django_comment_client/utils.py +++ b/lms/djangoapps/discussion/django_comment_client/utils.py @@ -24,10 +24,10 @@ from lms.djangoapps.discussion.django_comment_client.constants import TYPE_ENTRY, TYPE_SUBCATEGORY from lms.djangoapps.discussion.django_comment_client.permissions import ( check_permissions_by_view, - get_team, - has_permission + get_team ) from lms.djangoapps.discussion.django_comment_client.settings import MAX_COMMENT_DEPTH +from openedx.core.djangoapps.django_comment_common.models import has_permission from openedx.core.djangoapps.course_groups.cohorts import get_cohort_id from openedx.core.djangoapps.discussions.utils import ( get_accessible_discussion_xblocks, diff --git a/lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html b/lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html index f88c33440ce7..90f03999d539 100644 --- a/lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html +++ b/lms/djangoapps/discussion/templates/discussion/discussion_profile_page.html @@ -11,7 +11,7 @@ from django.template.defaultfilters import escapejs from django.urls import reverse -from lms.djangoapps.discussion.django_comment_client.permissions import has_permission +from openedx.core.djangoapps.django_comment_common.models import has_permission from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string %> diff --git a/lms/djangoapps/discussion/views.py b/lms/djangoapps/discussion/views.py index d6f61d209433..bca6cb7768de 100644 --- a/lms/djangoapps/discussion/views.py +++ b/lms/djangoapps/discussion/views.py @@ -28,6 +28,7 @@ import lms.djangoapps.discussion.django_comment_client.utils as utils import openedx.core.djangoapps.django_comment_common.comment_client as cc +from openedx.core.djangoapps.django_comment_common.models import has_permission from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff from common.djangoapps.util.json_request import JsonResponse, expect_json @@ -37,7 +38,6 @@ from lms.djangoapps.discussion.config.settings import is_forum_daily_digest_enabled from lms.djangoapps.discussion.django_comment_client.base.views import track_thread_viewed_event from lms.djangoapps.discussion.django_comment_client.constants import TYPE_ENTRY -from lms.djangoapps.discussion.django_comment_client.permissions import has_permission from lms.djangoapps.discussion.django_comment_client.utils import ( add_courseware_context, course_discussion_division_enabled, diff --git a/openedx/core/djangoapps/discussions/services.py b/openedx/core/djangoapps/discussions/services.py new file mode 100644 index 000000000000..8df33c6f3e98 --- /dev/null +++ b/openedx/core/djangoapps/discussions/services.py @@ -0,0 +1,36 @@ +""" +Discussion Configuration Service for XBlock runtime. + +This service provides discussion-related configuration and feature flags +that are specific to the edx-platform implementation +for the extracted discussion block in xblocks-contrib repository. +""" + +from django.conf import settings +from openedx.core.djangoapps.django_comment_common.models import has_permission +from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, Provider + + +class DiscussionConfigService: + """ + Service for providing discussion-related configuration and feature flags. + """ + + def has_permission(self, user, permission, course_id=None): + """ + Return the discussion permission for a user in a given course. + """ + return has_permission(user, permission, course_id) + + def is_discussion_visible(self, course_key): + """ + Discussion Xblock does not support new OPEN_EDX provider + """ + provider = DiscussionsConfiguration.get(course_key) + return provider.provider_type == Provider.LEGACY + + def is_discussion_enabled(self): + """ + Return True if discussions are enabled; else False + """ + return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE') diff --git a/openedx/core/djangoapps/django_comment_common/models.py b/openedx/core/djangoapps/django_comment_common/models.py index bd7b8fe66e67..51863c42d472 100644 --- a/openedx/core/djangoapps/django_comment_common/models.py +++ b/openedx/core/djangoapps/django_comment_common/models.py @@ -14,6 +14,8 @@ from django.utils.translation import gettext_noop from jsonfield.fields import JSONField from opaque_keys.edx.django.models import CourseKeyField +from edx_django_utils.cache import DEFAULT_REQUEST_CACHE +from opaque_keys.edx.keys import CourseKey from openedx.core.djangoapps.xmodule_django.models import NoneToEmptyManager from openedx.core.lib.cache_utils import request_cached @@ -193,6 +195,37 @@ def all_permissions_for_user_in_course(user, course_id): return permission_names +def has_permission(user, permission, course_id=None): + """ + This function resolves all discussion-related permissions for the given + user and course, caches them for the duration of the request, and verifies + whether the requested permission is present. + + Args: + user (User): Django user whose permissions are being checked. + permission (str): Discussion permission identifier + (e.g., "create_comment", "create_thread"). + course_id (CourseKey): Course context in which to evaluate + the permission + + Returns: + bool: True if the user has the specified permission in the given + course context; False otherwise. + """ + assert isinstance(course_id, (type(None), CourseKey)) + request_cache_dict = DEFAULT_REQUEST_CACHE.data + cache_key = "django_comment_client.permissions.has_permission.all_permissions.{}.{}".format( + user.id, course_id + ) + if cache_key in request_cache_dict: + all_permissions = request_cache_dict[cache_key] + else: + all_permissions = all_permissions_for_user_in_course(user, course_id) + request_cache_dict[cache_key] = all_permissions + + return permission in all_permissions + + class ForumsConfig(ConfigurationModel): """ Config for the connection to the cs_comments_service forums backend. diff --git a/openedx/core/djangoapps/xblock/runtime/runtime.py b/openedx/core/djangoapps/xblock/runtime/runtime.py index 2ae4a431bfbe..df3c8958de0a 100644 --- a/openedx/core/djangoapps/xblock/runtime/runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/runtime.py @@ -347,6 +347,9 @@ def service(self, block: XBlock, service_name: str): # Import here to avoid circular dependency from openedx.core.djangoapps.video_config.services import VideoConfigService return VideoConfigService() + elif service_name == 'discussion_config': + from openedx.core.djangoapps.discussions.services import DiscussionConfigService + return DiscussionConfigService() # Otherwise, fall back to the base implementation which loads services # defined in the constructor: diff --git a/openedx/features/course_bookmarks/templates/course_bookmarks/course-bookmarks.html b/openedx/features/course_bookmarks/templates/course_bookmarks/course-bookmarks.html index a7038b3bdae6..bda15a7431af 100644 --- a/openedx/features/course_bookmarks/templates/course_bookmarks/course-bookmarks.html +++ b/openedx/features/course_bookmarks/templates/course_bookmarks/course-bookmarks.html @@ -15,7 +15,7 @@ from django.utils.translation import gettext as _ from django.template.defaultfilters import escapejs -from lms.djangoapps.discussion.django_comment_client.permissions import has_permission +from openedx.core.djangoapps.django_comment_common.models import has_permission from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string from openedx.core.djangolib.markup import HTML from openedx.features.course_experience import course_home_page_title diff --git a/xmodule/discussion_block.py b/xmodule/discussion_block.py index aaea2de7bb2a..a27cff307118 100644 --- a/xmodule/discussion_block.py +++ b/xmodule/discussion_block.py @@ -17,7 +17,7 @@ from xblock.utils.studio_editable import StudioEditableXBlockMixin from xblocks_contrib.discussion import DiscussionXBlock as _ExtractedDiscussionXBlock -from lms.djangoapps.discussion.django_comment_client.permissions import has_permission +from openedx.core.djangoapps.django_comment_common.models import has_permission from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration, Provider from openedx.core.djangolib.markup import HTML, Text from openedx.core.lib.xblock_utils import get_css_dependencies, get_js_dependencies @@ -282,8 +282,17 @@ def _apply_metadata_and_policy(cls, block, node, runtime): setattr(block, field_name, value) -DiscussionXBlock = ( - _ExtractedDiscussionXBlock if settings.USE_EXTRACTED_DISCUSSION_BLOCK - else _BuiltInDiscussionXBlock -) +DiscussionXBlock = None + + +def reset_class(): + """Reset class as per django settings flag""" + global DiscussionXBlock + DiscussionXBlock = ( + _ExtractedDiscussionXBlock if settings.USE_EXTRACTED_DISCUSSION_BLOCK + else _BuiltInDiscussionXBlock + ) + return DiscussionXBlock + +reset_class() DiscussionXBlock.__name__ = "DiscussionXBlock"