* feat: adds SearchAccess model Stores a numeric ID for each course + library, which will generally be shorter than the full context_key, so we can pack more of them into the the Meilisearch search filter. Also: * Adds data migration pre-populates the SearchAccess model from the existing CourseOverview and ContentLibrary records * Adds signal handlers to add/remove SearchAccess entries when content is created or deleted. * Adds get_access_ids_for_request() helper method for use in views. * Adds tests. * test: can't import content.search in lms tests * feat: use SearchAccess in documents and views * Adds an access_id field to the document, which stores the SearchAccess.id for the block's context. * Use the requesting user's allowed access_ids to filter search results to documents with those access_ids. * Since some users have a lot of individual access granted, limit the number of access_ids in the filter to a large number (1_000) * Updates tests to demonstrate. * test: can't import content.search or content_staging in lms tests * fix: make access_id field filterable * fix: use SearchAccess.get_or_create in signal handlers In theory, we shouldn't have to do this, because the CREATE and DELETE events should keep the SearchAccess table up-to-date. But in practice, signals can be missed (or in tests, they may be disabled). So we assume that it's ok to re-use a SearchAccess.id created for a given course or library context_key. * refactor: refactors the view tests to make them clearer Uses helper methods and decorators to wrap the settings and patches used by multiple view tests. * feat: adds org filters to meilisearch filter * Uses content_tagging.rules.get_user_orgs to fetch the user's content-related orgs for use in the meilisearch filter. * Limits the number of orgs used to 1_000 to keep token size down * refactor: removes data migration Users should use the reindex_studio management command to populate SearchAccess. * refactor: adds functions to common.djangoapps.student.role_helpers to allow general access to the user's RoleCache without having to access private attributes of User or RoleCache. Related changes: * Moves some functionality from openedx.core.djangoapps.enrollments.data.get_user_roles to this new helper method. * Use these new helper method in content_tagging.rules * fix: get_access_ids_for_request only returns individual access instead of all course keys that the user can read. Org- and GlobalStaff access checks will handle the rest. * fix: use org-level permissions when generating search filter Also refactors tests to demonstrate this change for OrgStaff and OrgInstructor users. * refactor: remove SearchAccess creation signal handlers Lets SearchAccess entries be created on demand during search indexing. * feat: omit access_ids from the search filter that are covered by the user's org roles --------- Co-authored-by: Rômulo Penido <romulo.penido@gmail.com>
79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""
|
|
Helpers for student roles
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from django.contrib.auth import get_user_model
|
|
|
|
from openedx.core.djangoapps.django_comment_common.models import (
|
|
FORUM_ROLE_ADMINISTRATOR,
|
|
FORUM_ROLE_COMMUNITY_TA,
|
|
FORUM_ROLE_GROUP_MODERATOR,
|
|
FORUM_ROLE_MODERATOR,
|
|
Role
|
|
)
|
|
from openedx.core.lib.cache_utils import request_cached
|
|
from common.djangoapps.student.roles import (
|
|
CourseAccessRole,
|
|
CourseBetaTesterRole,
|
|
CourseInstructorRole,
|
|
CourseStaffRole,
|
|
GlobalStaff,
|
|
OrgInstructorRole,
|
|
OrgStaffRole,
|
|
RoleCache,
|
|
)
|
|
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
@request_cached()
|
|
def has_staff_roles(user, course_key):
|
|
"""
|
|
Return true if a user has any of the following roles
|
|
Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator
|
|
"""
|
|
forum_roles = [FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_GROUP_MODERATOR,
|
|
FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR]
|
|
is_staff = CourseStaffRole(course_key).has_user(user)
|
|
is_instructor = CourseInstructorRole(course_key).has_user(user)
|
|
is_beta_tester = CourseBetaTesterRole(course_key).has_user(user)
|
|
is_org_staff = OrgStaffRole(course_key.org).has_user(user)
|
|
is_org_instructor = OrgInstructorRole(course_key.org).has_user(user)
|
|
is_global_staff = GlobalStaff().has_user(user)
|
|
has_forum_role = Role.user_has_role_for_course(user, course_key, forum_roles)
|
|
if any([is_staff, is_instructor, is_beta_tester, is_org_staff,
|
|
is_org_instructor, is_global_staff, has_forum_role]):
|
|
return True
|
|
return False
|
|
|
|
|
|
@request_cached()
|
|
def get_role_cache(user: User) -> RoleCache:
|
|
"""
|
|
Returns a populated RoleCache for the given user.
|
|
|
|
The returned RoleCache is also cached on the provided `user` to improve performance on future roles checks.
|
|
|
|
:param user: User
|
|
:return: All roles for all courses that this user has access to.
|
|
"""
|
|
# pylint: disable=protected-access
|
|
if not hasattr(user, '_roles'):
|
|
user._roles = RoleCache(user)
|
|
return user._roles
|
|
|
|
|
|
@request_cached()
|
|
def get_course_roles(user: User) -> list[CourseAccessRole]:
|
|
"""
|
|
Returns a list of all course-level roles that this user has.
|
|
|
|
:param user: User
|
|
:return: All roles for all courses that this user has access to.
|
|
"""
|
|
# pylint: disable=protected-access
|
|
role_cache = get_role_cache(user)
|
|
return list(role_cache._roles)
|