Files
edx-platform/xmodule/partitions/partitions_service.py
Maria Grimaldi 809ffc3743 feat: connect teams with content groups using dynamic partition generator (#33788)
Implements the connection from the teams feature to the content groups feature. This implementation uses the dynamic partition generator extension point to associate content groups with the users that belong to a Team.

This implementation was heavily inspired by the enrollment tracks dynamic partitions.
2024-04-25 13:02:49 -04:00

184 lines
6.7 KiB
Python

"""
This is a service-like API that assigns tracks which groups users are in for various
user partitions. It uses the user_service key/value store provided by the LMS runtime to
persist the assignments.
"""
import logging
from typing import Dict
from django.conf import settings
from django.contrib.auth import get_user_model
from opaque_keys.edx.keys import CourseKey
from openedx.core.lib.cache_utils import request_cached
from openedx.core.lib.dynamic_partitions_generators import DynamicPartitionGeneratorsPluginManager
from xmodule.modulestore.django import modulestore
from xmodule.partitions.partitions import get_partition_from_id
from .partitions import Group
User = get_user_model()
log = logging.getLogger(__name__)
FEATURES = getattr(settings, 'FEATURES', {})
@request_cached()
def get_all_partitions_for_course(course, active_only=False):
"""
A method that returns all `UserPartitions` associated with a course, as a List.
This will include the ones defined in course.user_partitions, but it may also
include dynamically included partitions (such as the `EnrollmentTrackUserPartition`).
Args:
course: the course for which user partitions should be returned.
active_only: if `True`, only partitions with `active` set to True will be returned.
Returns:
A List of UserPartitions associated with the course.
"""
all_partitions = course.user_partitions + _get_dynamic_partitions(course)
if active_only:
all_partitions = [partition for partition in all_partitions if partition.active]
return all_partitions
def get_user_partition_groups(course_key: CourseKey, user_partitions: list, user: User,
partition_dict_key: str = 'name') -> Dict[str, Group]:
"""
Collect group ID for each partition in this course for this user.
Arguments:
course_key (CourseKey)
user_partitions (list[UserPartition])
user (User)
partition_dict_key - i.e. 'id', 'name' depending on which partition attribute you want as a key.
Returns:
dict[partition_dict_key: Group]: Mapping from user partitions to the group to
which the user belongs in each partition. If the user isn't
in a group for a particular partition, then that partition's
ID will not be in the dict.
"""
partition_groups = {}
for partition in user_partitions:
group = partition.scheme.get_group_for_user(
course_key,
user,
partition,
)
if group is not None:
partition_groups[getattr(partition, partition_dict_key)] = group
return partition_groups
def _get_dynamic_partitions(course):
"""
Return the dynamic user partitions for this course.
If none exists, returns an empty array.
"""
dynamic_partition_generators = DynamicPartitionGeneratorsPluginManager.get_available_plugins().values()
generated_partitions = []
for generator in dynamic_partition_generators:
generated_partition = generator(course)
if generated_partition:
# If the generator returns a list of partitions, add them all to the list.
# Otherwise, just add the single partition. This is needed for cases where
# a single generator can return multiple partitions, such as the TeamUserPartition.
if isinstance(generated_partition, list):
generated_partitions.extend(generated_partition)
else:
generated_partitions.append(generated_partition)
return generated_partitions
class PartitionService:
"""
This is an XBlock service that returns information about the user partitions associated
with a given course.
"""
def __init__(self, course_id, cache=None, course=None):
self._course_id = course_id
self._cache = cache
self.course = course
def get_course(self):
"""
Return the course instance associated with this PartitionService.
This default implementation looks up the course from the modulestore.
"""
return self.course or modulestore().get_course(self._course_id)
@property
def course_partitions(self):
"""
Return the set of partitions assigned to self._course_id (both those set directly on the course
through course.user_partitions, and any dynamic partitions that exist). Note: this returns
both active and inactive partitions.
"""
if course := self.get_course():
return get_all_partitions_for_course(course)
return []
def get_user_group_id_for_partition(self, user, user_partition_id):
"""
If the user is already assigned to a group in user_partition_id, return the
group_id.
If not, assign them to one of the groups, persist that decision, and
return the group_id.
Args:
user_partition_id -- an id of a partition that's hopefully in the
runtime.user_partitions list.
Returns:
The id of one of the groups in the specified user_partition_id (as a string).
Raises:
ValueError if the user_partition_id isn't found.
"""
cache_key = "PartitionService.ugidfp.{}.{}.{}".format(
user.id, self._course_id, user_partition_id
)
if self._cache and (cache_key in self._cache):
return self._cache[cache_key]
user_partition = self.get_user_partition(user_partition_id)
if user_partition is None:
raise ValueError(
"Configuration problem! No user_partition with id {} "
"in course {}".format(user_partition_id, self._course_id)
)
group = self.get_group(user, user_partition)
group_id = group.id if group else None
if self._cache is not None:
self._cache[cache_key] = group_id
return group_id
def get_user_partition(self, user_partition_id):
"""
Look for a user partition with a matching id in the course's partitions.
Note that this method can return an inactive user partition.
Returns:
A UserPartition, or None if not found.
"""
return get_partition_from_id(self.course_partitions, user_partition_id)
def get_group(self, user, user_partition, assign=True):
"""
Returns the group from the specified user partition to which the user is assigned.
If the user has not yet been assigned, a group will be chosen for them based upon
the partition's scheme.
"""
return user_partition.scheme.get_group_for_user(
self._course_id, user, user_partition, assign=assign,
)