-
Notifications
You must be signed in to change notification settings - Fork 4
feat: implement discussion mute/unmute feature with user and staff-level controls #54
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?
Conversation
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.
Pull request overview
This PR implements a comprehensive mute/unmute feature for discussion forums, enabling users to manage unwanted interactions with role-based controls. The implementation adds personal and course-wide mute capabilities for learners and staff, with retroactive content filtering and proper permission checks.
Changes:
- Added five new API endpoints for mute/unmute operations with dual implementations
- Implemented content filtering to hide posts from muted users
- Added permission checks to prevent learners from muting staff and self-muting
- Integrated with forum service backend for mute record management
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| views.py | Added MuteUserView, UnmuteUserView, MuteAndReportView, MutedUsersListView, and MuteStatusView endpoints |
| urls.py | Registered new mute/unmute endpoints including duplicate forum-prefixed routes |
| permissions.py | Added can_mute_user, can_unmute_user, can_view_muted_users functions and CanMuteUsers permission class |
| serializers.py | Added 9 new serializers for mute request/response handling |
| forms.py | Added include_muted field to ThreadListGetForm and CommentListGetForm |
| api.py | Added get_muted_user_ids and filter_muted_content functions with integration into thread/comment retrieval |
| forum_integration.py | New service layer for forum API integration with ForumMuteService and ForumIntegrationService classes |
| forum_mute_views.py | Duplicate implementation of mute views prefixed with "Forum" |
| test files | Added test coverage for permissions and updated existing tests with mocking |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| content_type=content_type, | ||
| content_object_id=thread_id, | ||
| user=request.user, | ||
| defaults={'flagged_at': datetime.now()} |
Copilot
AI
Jan 16, 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.
Using datetime.now() instead of timezone.now(). This creates a naive datetime which can cause timezone-related bugs. The code imports timezone from django.utils (line 14) and uses timezone.now() elsewhere (line 2122), but here uses datetime.now(). This inconsistency should be fixed.
| content_type=content_type, | ||
| content_object_id=comment_id, | ||
| user=request.user, | ||
| defaults={'flagged_at': datetime.now()} |
Copilot
AI
Jan 16, 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.
Using datetime.now() instead of timezone.now(). This creates a naive datetime which can cause timezone-related bugs. Should use timezone.now() for consistency with the rest of the codebase and to ensure timezone-aware datetimes.
| # Import new forum-based mute views | ||
| from lms.djangoapps.discussion.rest_api.forum_mute_views import ( | ||
| ForumMuteUserView, | ||
| ForumUnmuteUserView, | ||
| ForumMuteAndReportView, | ||
| ForumMutedUsersListView, | ||
| ForumMuteStatusView, | ||
| ) |
Copilot
AI
Jan 16, 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 includes two complete implementations of the mute functionality: one set in views.py (MuteUserView, UnmuteUserView, etc.) and a duplicate set in forum_mute_views.py with 'Forum' prefixes. This creates significant code duplication (~590 lines duplicated). The two implementations are nearly identical with only minor variations. Consider consolidating to a single implementation to reduce maintenance burden and potential for bugs.
| # Import new forum-based mute views | |
| from lms.djangoapps.discussion.rest_api.forum_mute_views import ( | |
| ForumMuteUserView, | |
| ForumUnmuteUserView, | |
| ForumMuteAndReportView, | |
| ForumMutedUsersListView, | |
| ForumMuteStatusView, | |
| ) |
| username = raw_data.get('username') | ||
| is_course_wide = raw_data.get('is_course_wide', False) | ||
|
|
||
| log.info(f"[UNMUTE_DEBUG] Received username={username}, is_course_wide={is_course_wide}") |
Copilot
AI
Jan 16, 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.
Debug logging with '[UNMUTE_DEBUG]' prefix should be removed before merging to production. Debug log statements are appropriate during development but should be cleaned up for production code.
| log.info(f"[UNMUTE_DEBUG] Received username={username}, is_course_wide={is_course_wide}") |
| log.info( | ||
| '[UNMUTE_DEBUG] scope=%s, muted_user_id=%s, course_id=%s', | ||
| data.get("scope", "personal"), target_user.id, str(course_key) | ||
| ) |
Copilot
AI
Jan 16, 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.
Debug logging with '[UNMUTE_DEBUG]' prefix should be removed before merging to production. Debug log statements are appropriate during development but should be cleaned up for production code.
| log.info( | |
| '[UNMUTE_DEBUG] scope=%s, muted_user_id=%s, course_id=%s', | |
| data.get("scope", "personal"), target_user.id, str(course_key) | |
| ) |
| if thread_id: | ||
| # Report thread using AbuseFlagger | ||
| try: | ||
| forum_thread = ForumThread.objects.get(pk=thread_id) |
Copilot
AI
Jan 16, 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.
Variable forum_thread is not used.
| forum_thread = ForumThread.objects.get(pk=thread_id) | |
| ForumThread.objects.get(pk=thread_id) |
| if thread: | ||
| thread.flagAbuse(request.user, voteable=thread) | ||
|
|
||
| report_record = { |
Copilot
AI
Jan 16, 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.
Variable report_record is not used.
| thread = Thread.find(thread_id) | ||
| if thread: | ||
| thread.flagAbuse(request.user, voteable=thread) | ||
| report_record = { |
Copilot
AI
Jan 16, 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.
Variable report_record is not used.
| elif comment_id: | ||
| # Report comment using AbuseFlagger | ||
| try: | ||
| forum_comment = ForumComment.objects.get(pk=comment_id) |
Copilot
AI
Jan 16, 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.
Variable forum_comment is not used.
| forum_comment = ForumComment.objects.get(pk=comment_id) | |
| ForumComment.objects.get(pk=comment_id) |
| if comment: | ||
| comment.flagAbuse(request.user, voteable=comment) | ||
|
|
||
| report_record = { |
Copilot
AI
Jan 16, 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.
Variable report_record is not used.
Description
This update introduces a comprehensive Mute / Unmute feature for discussion forums, enabling learners and staff to manage unwanted interactions more effectively while maintaining a healthy learning environment. The feature supports both personal and course-wide mute scopes, with clear role-based restrictions and overrides.
The implementation ensures muted content is hidden retroactively as well as for future posts, without notifying muted users. Special handling is included to prevent learners from muting staff or themselves, while giving staff full moderation control across the course.
Features
Learner Capabilities
Staff Capabilities
Key Behaviors
Linked PRs