-
Notifications
You must be signed in to change notification settings - Fork 10
fix: resolve cross-team discussion visibility issue #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release-ulmo
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -101,6 +101,25 @@ def make_course_settings(course, user, include_category_map=True): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return course_setting | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _filter_team_discussions(threads, course_key, user): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Filter team discussions - only include team discussions if user is a team member. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Privileged users (staff, moderators, etc.) can see all threads. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if is_privileged_user(course_key, user): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return threads | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filtered_threads = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for thread in threads: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thread_discussion_id = thread.get('commentable_id') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if thread_discussion_id: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| team = team_api.get_team_by_discussion(thread_discussion_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if team and not team.users.filter(id=user.id).exists(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue # Skip team threads where user is not a member | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+112
to
+118
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filtered_threads = [] | |
| for thread in threads: | |
| thread_discussion_id = thread.get('commentable_id') | |
| if thread_discussion_id: | |
| team = team_api.get_team_by_discussion(thread_discussion_id) | |
| if team and not team.users.filter(id=user.id).exists(): | |
| continue # Skip team threads where user is not a member | |
| # Cache teams per discussion ID and membership per team to avoid N+1/2N queries. | |
| discussion_ids = { | |
| thread.get('commentable_id') | |
| for thread in threads | |
| if thread.get('commentable_id') | |
| } | |
| teams_by_discussion = {} | |
| for discussion_id in discussion_ids: | |
| teams_by_discussion[discussion_id] = team_api.get_team_by_discussion(discussion_id) | |
| user_membership_by_team_id = {} | |
| filtered_threads = [] | |
| for thread in threads: | |
| thread_discussion_id = thread.get('commentable_id') | |
| if thread_discussion_id: | |
| team = teams_by_discussion.get(thread_discussion_id) | |
| if team: | |
| team_id = getattr(team, "id", None) | |
| if team_id is not None: | |
| if team_id not in user_membership_by_team_id: | |
| user_membership_by_team_id[team_id] = team.users.filter(id=user.id).exists() | |
| if not user_membership_by_team_id[team_id]: | |
| # Skip team threads where user is not a member | |
| continue |
Copilot
AI
Apr 2, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR changes access control behavior for multiple thread-list endpoints (legacy views + REST API). There are existing discussion and REST API test suites in this repo; adding/adjusting tests to cover the new team-level filtering (including ensuring annotated_content_info does not include filtered-out threads) would help prevent regressions.
| annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) | |
| # Filter team discussions - only include team discussions if user is a team member | |
| threads = _filter_team_discussions(threads, course_key, request.user) | |
| # Filter team discussions - only include team discussions if user is a team member | |
| threads = _filter_team_discussions(threads, course_key, request.user) | |
| with function_trace("get_metadata_for_threads"): | |
| annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rest_api/api.pyimports_filter_team_discussionsfromdiscussion.views, even though the leading underscore indicates a private helper. To avoid a hard dependency on a private view-layer function (and make reuse intentional), consider moving this logic into a shared utility module (e.g.,lms.djangoapps.discussion.utilsorrest_api/utils.py) with a public name, and import it from there.