Files
edx-platform/openedx/core/djangoapps/discussions/utils.py
Mohammad Ahtasham ul Hassan 44fa09eba5 refactor: refactor discussions_xblock (#30636)
JIRA: https://openedx.atlassian.net/browse/BOM-2580
This PR aims at refactoring the discussion xblock sub project and moving it within the xmodule directory effectively removing its position as a sub project within edx-platform
2022-06-27 17:11:56 +05:00

194 lines
7.6 KiB
Python

"""
Shared utility code related to discussions.
"""
import logging
from typing import Dict, List, Optional, Tuple
from opaque_keys.edx.keys import CourseKey
from lms.djangoapps.courseware.access import has_access
from openedx.core.djangoapps.course_groups.cohorts import get_cohort_names, is_course_cohorted
from openedx.core.djangoapps.django_comment_common.models import CourseDiscussionSettings
from openedx.core.lib.cache_utils import request_cached
from openedx.core.lib.courses import get_course_by_id
from xmodule.discussion_block import DiscussionXBlock
from openedx.core.types import User
from xmodule.course_module import CourseBlock # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID, Group # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.partitions.partitions_service import PartitionService # lint-amnesty, pylint: disable=wrong-import-order
log = logging.getLogger(__name__)
def get_divided_discussions(
course: CourseBlock,
discussion_settings: CourseDiscussionSettings,
) -> Tuple[List[str], List[str]]:
"""
Returns the course-wide and inline divided discussion ids separately.
"""
divided_course_wide_discussions = []
divided_inline_discussions = []
course_wide_discussions = [topic['id'] for __, topic in course.discussion_topics.items()]
all_discussions = get_discussion_categories_ids(course, None, include_all=True)
for divided_discussion_id in discussion_settings.divided_discussions:
if divided_discussion_id in course_wide_discussions:
divided_course_wide_discussions.append(divided_discussion_id)
elif divided_discussion_id in all_discussions:
divided_inline_discussions.append(divided_discussion_id)
return divided_course_wide_discussions, divided_inline_discussions
def get_discussion_categories_ids(course: CourseBlock, user: Optional[User], include_all: bool = False) -> List[str]:
"""
Returns a list of available ids of categories for the course that
are accessible to the given user.
Args:
course: Course for which to get the ids.
user: User to check for access.
include_all: Whether categories from all blocks should be included.
"""
accessible_discussion_ids = [
xblock.discussion_id for xblock in get_accessible_discussion_xblocks(course, user, include_all)
]
return course.top_level_discussion_topic_ids + accessible_discussion_ids
def get_accessible_discussion_xblocks(
course: CourseBlock,
user: Optional[User],
include_all: bool = False,
) -> List[DiscussionXBlock]:
"""
Return a list of all valid discussion xblocks in this course that
are accessible to the given user.
"""
include_all = include_all or getattr(user, 'is_community_ta', False)
return get_accessible_discussion_xblocks_by_course_id(course.id, user, include_all=include_all)
@request_cached()
def get_accessible_discussion_xblocks_by_course_id(
course_id: CourseKey,
user: Optional[User] = None,
include_all: bool = False
) -> List[DiscussionXBlock]:
"""
Return a list of all valid discussion xblocks in this course.
Checks for the given user's access if include_all is False.
"""
all_xblocks = modulestore().get_items(course_id, qualifiers={'category': 'discussion'}, include_orphans=False)
return [
xblock for xblock in all_xblocks
if has_required_keys(xblock) and (include_all or has_access(user, 'load', xblock, course_id))
]
def available_division_schemes(course_key: CourseKey) -> List[str]:
"""
Returns a list of possible discussion division schemes for this course.
This takes into account if cohorts are enabled and if there are multiple
enrollment tracks. If no schemes are available, returns an empty list.
Args:
course_key: CourseKey
Returns: list of possible division schemes (for example, CourseDiscussionSettings.COHORT)
"""
available_schemes = []
if is_course_cohorted(course_key):
available_schemes.append(CourseDiscussionSettings.COHORT)
if enrollment_track_group_count(course_key) > 1:
available_schemes.append(CourseDiscussionSettings.ENROLLMENT_TRACK)
return available_schemes
def has_required_keys(xblock: DiscussionXBlock):
"""
Returns True iff xblock has the proper attributes for generating metadata
with get_discussion_id_map_entry()
"""
for key in ('discussion_id', 'discussion_category', 'discussion_target'):
if getattr(xblock, key, None) is None:
log.debug(
"Required key '%s' not in discussion %s, leaving out of category map",
key,
xblock.location
)
return False
return True
def enrollment_track_group_count(course_key: CourseKey) -> int:
"""
Returns the count of possible enrollment track division schemes for this course.
Args:
course_key: CourseKey
Returns:
Count of enrollment track division scheme
"""
return len(_get_enrollment_track_groups(course_key))
def _get_enrollment_track_groups(course_key: CourseKey) -> List[Group]:
"""
Helper method that returns an array of the Groups in the EnrollmentTrackUserPartition for the given course.
If no such partition exists on the course, an empty array is returned.
"""
partition_service = PartitionService(course_key)
partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
return partition.groups if partition else []
def get_group_names_by_id(course_discussion_settings: CourseDiscussionSettings) -> Dict[str, str]:
"""
Creates of a dict of group_id to learner-facing group names, for the division_scheme
in use as specified by course_discussion_settings.
Args:
course_discussion_settings: CourseDiscussionSettings model instance
Returns: dict of group_id to learner-facing group names. If no division_scheme
is in use, returns an empty dict.
"""
division_scheme = get_course_division_scheme(course_discussion_settings)
course_key = course_discussion_settings.course_id
if division_scheme == CourseDiscussionSettings.COHORT:
return get_cohort_names(get_course_by_id(course_key))
elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK:
# We negate the group_ids from dynamic partitions so that they will not conflict
# with cohort IDs (which are an auto-incrementing integer field, starting at 1).
return {-1 * group.id: group.name for group in _get_enrollment_track_groups(course_key)}
else:
return {}
def get_course_division_scheme(course_discussion_settings: CourseDiscussionSettings) -> str:
"""
Returns the division scheme used by the course, from the course discussion settings.
Args:
course_discussion_settings (CourseDiscussionSettings): An instance of the CourseDiscussionSettings model
Returns:
(string) Returns 'cohort', 'enrollment_track' or 'none'
depending on the division scheme used by the course.
"""
division_scheme = course_discussion_settings.division_scheme
if (
division_scheme == CourseDiscussionSettings.COHORT and
not is_course_cohorted(course_discussion_settings.course_id)
):
division_scheme = CourseDiscussionSettings.NONE
elif (
division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK and
enrollment_track_group_count(course_discussion_settings.course_id) <= 1
):
division_scheme = CourseDiscussionSettings.NONE
return division_scheme