-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement mute/unmute feature #7
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: master
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 learners and staff to manage unwanted interactions. The implementation supports both personal and course-wide mute scopes with role-based restrictions, and includes support for both MySQL and MongoDB backends.
Changes:
- Added database models for
DiscussionMuteandDiscussionMuteExceptionwith appropriate constraints and indexes - Implemented API views and endpoints for mute, unmute, mute-and-report, and status checking operations
- Created backend implementations for both MySQL and MongoDB with consistent interfaces
- Added management commands for creating MongoDB indexes for mute functionality
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| forum/views/mutes.py | New API views for mute/unmute operations with error handling |
| forum/serializers/mute.py | Serializers for mute input/output with proper validation |
| forum/migrations/0006_add_discussion_mute_models.py | Database migration creating mute-related tables with constraints |
| forum/backends/mysql/models.py | Django models for DiscussionMute and DiscussionMuteException with validation |
| forum/backends/mysql/api.py | MySQL backend implementation of mute operations |
| forum/backends/mongodb/mutes.py | MongoDB models and operations for mute functionality |
| forum/backends/mongodb/api.py | MongoDB backend API implementation |
| forum/backends/backend.py | Abstract base class method signatures for mute operations |
| forum/api/mutes.py | High-level API layer delegating to appropriate backend |
| forum/management/commands/forum_create_mute_mongodb_indexes.py | MongoDB index creation for optimal mute query performance |
| Various test files | Import ordering improvements (PEP 8 compliance) |
| Various view/command files | Unused parameter prefixing with underscore |
| forum/admin.py | Admin interface registration for mute models |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| if not muter_id: | ||
| return Response( | ||
| {"error": "moderator_id is required"}, |
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.
The error message refers to 'moderator_id' but the actual parameter name extracted from request.data is 'muter_id' (line 109). The error message should say 'muter_id is required' to match the actual field name being validated.
| {"error": "moderator_id is required"}, | |
| {"error": "muter_id is required"}, |
|
|
||
| def get_all_muted_users_for_course( | ||
| course_id: str, | ||
| _requester_id: Optional[str] = None, |
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.
The parameter is prefixed with underscore indicating it's unused, but it should be passed to the backend call. The backend implementation expects 'requester_id' for filtering personal mutes. Either remove the underscore and pass it through, or document why it's intentionally unused.
| unmuted_by_id: str, | ||
| course_id: str, | ||
| scope: str = "personal", | ||
| muted_by_id: Optional[str] = None, |
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.
Parameter name 'muted_by_id' shadows the function parameter 'unmuted_by_id' conceptually and creates confusion. In unmute operations, this represents the original muter for personal scope unmutes. Consider renaming to 'original_muter_id' or 'filter_muted_by_id' for clarity.
| """Create a mute exception for course-wide mutes.""" | ||
| # TODO: Implement mute exception logic when needed | ||
| raise NotImplementedError("Mute exceptions not yet implemented") | ||
|
|
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.
The create_mute_exception method raises NotImplementedError, but the feature description mentions course-wide mutes override personal mutes and staff can unmute across all scopes. This suggests exceptions might be needed for the feature to work as described. Clarify if this is intentional technical debt or if the implementation is incomplete.
| """Create a mute exception for course-wide mutes.""" | |
| # TODO: Implement mute exception logic when needed | |
| raise NotImplementedError("Mute exceptions not yet implemented") | |
| """Create or reactivate a mute exception for course-wide mutes. | |
| This allows a specific user to be exempt from a course-wide mute | |
| on a given muted user. | |
| """ | |
| try: | |
| muted_user = User.objects.get(pk=muted_user_id) | |
| exception_user = User.objects.get(pk=exception_user_id) | |
| # Ensure idempotent behavior: reuse existing exception if present. | |
| exception, created = DiscussionMuteException.objects.get_or_create( | |
| muted_user=muted_user, | |
| exception_user=exception_user, | |
| course_id=course_id, | |
| ) | |
| # If the exception model tracks active state, reactivate as needed. | |
| if not created and hasattr(exception, "is_active"): | |
| if not getattr(exception, "is_active"): | |
| setattr(exception, "is_active", True) | |
| exception.save() | |
| # Prefer model's serialization helper if available. | |
| if hasattr(exception, "to_dict") and callable(exception.to_dict): | |
| return exception.to_dict() | |
| # Fallback minimal representation. | |
| data: dict[str, Any] = { | |
| "id": getattr(exception, "id", None), | |
| "muted_user_id": muted_user_id, | |
| "exception_user_id": exception_user_id, | |
| "course_id": course_id, | |
| } | |
| # Include active flag if present on the model. | |
| if hasattr(exception, "is_active"): | |
| data["is_active"] = getattr(exception, "is_active") | |
| return data | |
| except Exception as e: | |
| raise ValueError(f"Failed to create mute exception: {e}") from e |
| # Create a basic report record (placeholder implementation) | ||
| # In a full implementation, this would integrate with a proper reporting system | ||
| report_result = { | ||
| "status": "success", | ||
| "report_id": f"report_{muted_user_id}_{muted_by_id}_{course_id}", | ||
| "reported_user_id": muted_user_id, | ||
| "reported_by_id": muted_by_id, | ||
| "course_id": course_id, | ||
| "reason": reason, | ||
| "created": datetime.utcnow().isoformat(), |
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.
The mute_and_report_user function creates a placeholder report that is never persisted. This means the 'report' part of 'mute and report' doesn't actually happen. The backend implementations call mute_user and add a 'reported' flag, but no actual report is created in any system. This could mislead users who expect reporting functionality to work.
| # MongoDB implementation placeholder | ||
| return { | ||
| "action_type": action_type, | ||
| "target_user_id": target_user_id, | ||
| "moderator_id": moderator_id, | ||
| "course_id": course_id, | ||
| "scope": scope, | ||
| "reason": reason, | ||
| "metadata": metadata or {}, | ||
| "backend": "mongodb", |
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.
The log_moderation_action method returns placeholder data without persisting logs. While the main mute/unmute operations do log actions via DiscussionModerationLogs, this standalone method doesn't persist anything, which could confuse callers expecting actual logging.
| """Log a moderation action.""" | ||
| # TODO: Implement moderation logging when needed | ||
| log_entry = { | ||
| "action_type": action_type, | ||
| "target_user_id": target_user_id, | ||
| "moderator_id": moderator_id, | ||
| "course_id": course_id, | ||
| "scope": scope, | ||
| "reason": reason, | ||
| "metadata": metadata or {}, | ||
| "timestamp": timezone.now().isoformat(), | ||
| } | ||
| # For now, just return the log entry data | ||
| return log_entry |
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.
The log_moderation_action method in MySQL backend also returns placeholder data. While mute/unmute operations do log to ModerationAuditLog, this standalone logging method doesn't persist anything. Consider implementing actual persistence or removing this method if it's not needed.
| """Log a moderation action.""" | |
| # TODO: Implement moderation logging when needed | |
| log_entry = { | |
| "action_type": action_type, | |
| "target_user_id": target_user_id, | |
| "moderator_id": moderator_id, | |
| "course_id": course_id, | |
| "scope": scope, | |
| "reason": reason, | |
| "metadata": metadata or {}, | |
| "timestamp": timezone.now().isoformat(), | |
| } | |
| # For now, just return the log entry data | |
| return log_entry | |
| """Log a moderation action. | |
| Note: | |
| This method is currently not implemented and will raise an error | |
| if called. Use the existing moderation audit logging mechanisms | |
| (e.g., those used by mute/unmute operations) instead. | |
| """ | |
| # TODO: Implement moderation logging persistence when requirements are defined | |
| raise NotImplementedError("Moderation action logging not yet implemented") |
| try: | ||
| # MongoDB implementation placeholder | ||
| return [] | ||
| except Exception as e: | ||
| raise ForumV2RequestError(f"Failed to get muted users: {str(e)}") from e | ||
|
|
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 statement is unreachable.
| try: | |
| # MongoDB implementation placeholder | |
| return [] | |
| except Exception as e: | |
| raise ForumV2RequestError(f"Failed to get muted users: {str(e)}") from e | |
| # MongoDB implementation placeholder | |
| # Returns an empty list until MongoDB support is implemented. | |
| return [] |
| try: | ||
| # MongoDB implementation placeholder | ||
| return { | ||
| "muted_user_id": muted_user_id, | ||
| "exception_user_id": exception_user_id, | ||
| "course_id": course_id, | ||
| "backend": "mongodb", | ||
| } | ||
| except Exception as e: | ||
| raise ForumV2RequestError( | ||
| f"Failed to create mute exception: {str(e)}" | ||
| ) from e |
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 statement is unreachable.
| try: | |
| # MongoDB implementation placeholder | |
| return { | |
| "muted_user_id": muted_user_id, | |
| "exception_user_id": exception_user_id, | |
| "course_id": course_id, | |
| "backend": "mongodb", | |
| } | |
| except Exception as e: | |
| raise ForumV2RequestError( | |
| f"Failed to create mute exception: {str(e)}" | |
| ) from e | |
| # MongoDB implementation placeholder | |
| return { | |
| "muted_user_id": muted_user_id, | |
| "exception_user_id": exception_user_id, | |
| "course_id": course_id, | |
| "backend": "mongodb", | |
| } |
| "metadata": metadata or {}, | ||
| "backend": "mongodb", | ||
| } | ||
| except Exception as e: |
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 statement is unreachable.
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