diff --git a/forum/models/comments.py b/forum/models/comments.py index 2b216c45..521e5ee0 100644 --- a/forum/models/comments.py +++ b/forum/models/comments.py @@ -25,6 +25,9 @@ def insert( anonymous: bool = False, anonymous_to_peers: bool = False, depth: int = 0, + abuse_flaggers: Optional[List[str]] = None, + historical_abuse_flaggers: Optional[List[str]] = None, + visible: bool = True, ) -> str: """ Inserts a new comment document into the database. @@ -38,16 +41,24 @@ def insert( anonymous (bool, optional): Whether the comment is posted anonymously. Defaults to False. anonymous_to_peers (bool, optional): Whether the comment is anonymous to peers. Defaults to False. depth (int, optional): The depth of the comment in the thread hierarchy. Defaults to 0. + abuse_flaggers (Optional[List[str]], optional): Users who flagged the comment. Defaults to None. + historical_abuse_flaggers (Optional[List[str]], optional): Users historically flagged the comment. + visible (bool, optional): Whether the comment is visible. Defaults to True. Returns: str: The ID of the inserted document. """ + if abuse_flaggers is None: + abuse_flaggers = [] + if historical_abuse_flaggers is None: + historical_abuse_flaggers = [] + date = datetime.now() comment_data = { "votes": self.get_votes_dict(up=[], down=[]), - "visible": True, - "abuse_flaggers": [], - "historical_abuse_flaggers": [], + "visible": visible, + "abuse_flaggers": abuse_flaggers, + "historical_abuse_flaggers": historical_abuse_flaggers, "parent_ids": [], "at_position_list": [], "body": body, @@ -81,6 +92,7 @@ def update( author_username: Optional[str] = None, votes: Optional[Dict[str, int]] = None, abuse_flaggers: Optional[List[str]] = None, + historical_abuse_flaggers: Optional[List[str]] = None, endorsed: Optional[bool] = None, child_count: Optional[int] = None, depth: Optional[int] = None, @@ -101,6 +113,7 @@ def update( author_username (Optional[str], optional): The username of the author. votes (Optional[Dict[str, int]], optional): The votes for the comment. abuse_flaggers (Optional[List[str]], optional): A list of users who flagged the comment for abuse. + historical_abuse_flaggers (Optional[List[str]], optional): Users who historically flagged the comment. endorsed (Optional[bool], optional): Whether the comment is endorsed. child_count (Optional[int], optional): The number of child comments. depth (Optional[int], optional): The depth of the comment in the thread hierarchy. @@ -120,6 +133,7 @@ def update( ("author_username", author_username), ("votes", votes), ("abuse_flaggers", abuse_flaggers), + ("historical_abuse_flaggers", historical_abuse_flaggers), ("endorsed", endorsed), ("child_count", child_count), ("depth", depth), diff --git a/forum/models/threads.py b/forum/models/threads.py index cbb579a0..34b488e5 100644 --- a/forum/models/threads.py +++ b/forum/models/threads.py @@ -28,6 +28,9 @@ def insert( thread_type: str = "discussion", context: str = "course", pinned: bool = False, + visible: bool = True, + abuse_flaggers: Optional[List[str]] = None, + historical_abuse_flaggers: Optional[List[str]] = None, ) -> str: """ Inserts a new thread document into the database. @@ -44,6 +47,9 @@ def insert( thread_type (str, optional): The type of the thread, either 'question' or 'discussion'. context (str, optional): The context of the thread, either 'course' or 'standalone'. pinned (bool): Whether the thread is pinned. Defaults to False. + visible (bool): Whether the thread is visible. Defaults to True. + abuse_flaggers: A list of users who flagged the thread for abuse. + historical_abuse_flaggers: A list of users who historically flagged the thread for abuse. Raises: ValueError: If `thread_type` is not 'question' or 'discussion'. @@ -54,15 +60,16 @@ def insert( """ if thread_type not in ["question", "discussion"]: raise ValueError("Invalid thread_type") - if context not in ["course", "standalone"]: raise ValueError("Invalid context") + if abuse_flaggers is None: + abuse_flaggers = [] + if historical_abuse_flaggers is None: + historical_abuse_flaggers = [] date = datetime.now() thread_data = { "votes": self.get_votes_dict(up=[], down=[]), - "abuse_flaggers": [], - "historical_abuse_flaggers": [], "thread_type": thread_type, "context": context, "comment_count": 0, @@ -81,6 +88,9 @@ def insert( "updated_at": date, "last_activity_at": date, "pinned": pinned, + "visible": visible, + "abuse_flaggers": abuse_flaggers, + "historical_abuse_flaggers": historical_abuse_flaggers, } result = self._collection.insert_one(thread_data) return str(result.inserted_id) @@ -102,6 +112,7 @@ def update( author_username: Optional[str] = None, votes: Optional[Dict[str, int]] = None, abuse_flaggers: Optional[List[str]] = None, + historical_abuse_flaggers: Optional[List[str]] = None, closed_by: Optional[str] = None, pinned: Optional[bool] = None, comments_count: Optional[int] = None, @@ -126,6 +137,7 @@ def update( author_username: The username of the author. votes: The votes for the thread. abuse_flaggers: A list of users who flagged the thread for abuse. + historical_abuse_flaggers: A list of users who historically flagged the thread for abuse. closed_by: The ID of the user who closed the thread. pinned: Whether the thread is pinned. comments_count: The number of comments on the thread. @@ -149,6 +161,7 @@ def update( ("author_username", author_username), ("votes", votes), ("abuse_flaggers", abuse_flaggers), + ("historical_abuse_flaggers", historical_abuse_flaggers), ("closed_by", closed_by), ("pinned", pinned), ("comments_count", comments_count), diff --git a/forum/views/flags.py b/forum/views/flags.py index f8799ae8..d60ba030 100644 --- a/forum/views/flags.py +++ b/forum/views/flags.py @@ -6,13 +6,14 @@ from rest_framework.response import Response from rest_framework.views import APIView -from forum.models import Contents, Users +from forum.models import Comment, CommentThread, Users from forum.models.model_utils import ( flag_as_abuse, un_flag_all_as_abuse, un_flag_as_abuse, ) -from forum.serializers.contents import ContentSerializer +from forum.serializers.comment import UserCommentSerializer +from forum.serializers.thread import UserThreadSerializer class CommentFlagAPIView(APIView): @@ -38,24 +39,39 @@ def put(self, request: Request, comment_id: str, action: str) -> Response: """ request_data = request.data user = Users().get(request_data["user_id"]) - content = Contents().get(comment_id) - if not (user and content): + comment = Comment().get(comment_id) + if not (user and comment): return Response( {"error": "User / Comment doesn't exist"}, status=status.HTTP_400_BAD_REQUEST, ) if action == "flag": - comment = flag_as_abuse(user, content) + updated_comment = flag_as_abuse(user, comment) elif action == "unflag": if request_data.get("all") and request_data.get("all") is True: - comment = un_flag_all_as_abuse(content) + updated_comment = un_flag_all_as_abuse(comment) else: - comment = un_flag_as_abuse(user, content) + updated_comment = un_flag_as_abuse(user, comment) else: return Response( {"error": "Invalid action"}, status=status.HTTP_400_BAD_REQUEST ) - serializer = ContentSerializer(comment) + + if updated_comment is None: + return Response( + {"error": "Failed to update comment"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + context = { + "id": str(updated_comment["_id"]), + **updated_comment, + "user_id": user["_id"], + "username": user["username"], + "type": "comment", + "thread_id": str(updated_comment.get("comment_thread_id", None)), + } + serializer = UserCommentSerializer(context) return Response(serializer.data, status=status.HTTP_200_OK) @@ -82,23 +98,38 @@ def put(self, request: Request, thread_id: str, action: str) -> Response: """ request_data = request.data user = Users().get(request_data["user_id"]) - content = Contents().get(thread_id) - if not (user and content): + thread = CommentThread().get(thread_id) + if not (user and thread): return Response( - {"error": "User / Comment doesn't exist"}, + {"error": "User / Thread doesn't exist"}, status=status.HTTP_400_BAD_REQUEST, ) if action == "flag": - thread = flag_as_abuse(user, content) + updated_thread = flag_as_abuse(user, thread) elif action == "unflag": if request_data.get("all"): - thread = un_flag_all_as_abuse(content) + updated_thread = un_flag_all_as_abuse(thread) else: - thread = un_flag_as_abuse(user, content) + updated_thread = un_flag_as_abuse(user, thread) else: return Response( {"error": "Invalid action"}, status=status.HTTP_400_BAD_REQUEST, ) - serializer = ContentSerializer(thread) + + if updated_thread is None: + return Response( + {"error": "Failed to update thread"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + context = { + "id": str(updated_thread["_id"]), + **updated_thread, + "user_id": user["_id"], + "username": user["username"], + "type": "thread", + "thread_id": str(updated_thread.get("comment_thread_id", None)), + } + serializer = UserThreadSerializer(context) return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/tests/test_views/test_flags.py b/tests/test_views/test_flags.py index 4cf78e0c..7de3adbd 100644 --- a/tests/test_views/test_flags.py +++ b/tests/test_views/test_flags.py @@ -1,8 +1,6 @@ """Test flags api endpoints.""" -from unittest.mock import Mock, patch - -from forum.models import Contents, Users +from forum.models import Comment, CommentThread, Users from test_utils.client import APIClient @@ -13,35 +11,34 @@ def test_comment_thread_api(api_client: APIClient) -> None: This test checks that a user can flag a comment thread for abuse and then unflag it. """ user_id = "1" - comment_thread_id = "66ace22474ba69001e1440cd" Users().insert(user_id, username="user1", email="email1") - Contents().insert( - comment_thread_id, - "3", + comment_thread_id = CommentThread().insert( + "Thread 1", + "Body 1", + "_type", + "CommentThread", + "1", + "user1", abuse_flaggers=[], historical_abuse_flaggers=[], - visible=True, ) - mock_users_class = Mock(return_value=Users()) - mock_contents_class = Mock(return_value=Contents()) - with patch("forum.models.Users", new=mock_users_class): - with patch("forum.models.Contents", new=mock_contents_class): - response = api_client.put_json( - f"/api/v2/threads/{comment_thread_id}/abuse_flag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 200 - comment_thread = response.json() - assert comment_thread["abuse_flaggers"] == [str(user_id)] - response = api_client.put_json( - path=f"/api/v2/threads/{comment_thread_id}/abuse_unflag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 200 - comment = Contents().get(comment_thread_id) - assert comment is not None - assert comment["abuse_flaggers"] == [] + response = api_client.put_json( + f"/api/v2/threads/{comment_thread_id}/abuse_flag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 200 + comment_thread = response.json() + assert comment_thread["abuse_flaggers"] == [str(user_id)] + + response = api_client.put_json( + path=f"/api/v2/threads/{comment_thread_id}/abuse_unflag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 200 + comment = CommentThread().get(comment_thread_id) + assert comment is not None + assert comment["abuse_flaggers"] == [] def test_comment_flag_api(api_client: APIClient) -> None: @@ -51,44 +48,45 @@ def test_comment_flag_api(api_client: APIClient) -> None: This test checks that a user can flag a comment for abuse and then unflag it. """ user_id = "1" - comment_id = "66ace22474ba69001e1440cd" + comment_thread_id = "66ace22474ba69001e1440cd" + author_id = "4" + author_username = "edly" + course_id = "course-xyz" Users().insert(user_id, username="user1", email="email1") - Contents().insert( - comment_id, - "3", + comment_id = Comment().insert( + "
Comment 1
", + course_id, + comment_thread_id, + author_id, + author_username, abuse_flaggers=[], historical_abuse_flaggers=[], - visible=True, ) - mock_users_class = Mock(return_value=Users()) - mock_contents_class = Mock(return_value=Contents()) - with patch("forum.models.Users", new=mock_users_class): - with patch("forum.models.Contents", new=mock_contents_class): - response = api_client.put_json( - f"/api/v2/comments/{comment_id}/abuse_flag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 200 - comment_thread = response.json() - assert comment_thread["abuse_flaggers"] == [str(user_id)] + response = api_client.put_json( + f"/api/v2/comments/{comment_id}/abuse_flag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 200 + comment_thread = response.json() + assert comment_thread["abuse_flaggers"] == [str(user_id)] - response = api_client.put_json( - path=f"/api/v2/comments/{comment_id}/abuse_unflag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 200 - comment = Contents().get(comment_id) - assert comment is not None - assert comment["abuse_flaggers"] == [] + response = api_client.put_json( + path=f"/api/v2/comments/{comment_id}/abuse_unflag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 200 + comment = Comment().get(comment_id) + assert comment is not None + assert comment["abuse_flaggers"] == [] - response = api_client.put_json( - path=f"/api/v2/comments/{comment_id}/abuse_unflag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 200 - comment = Contents().get(comment_id) - assert comment is not None - assert comment["abuse_flaggers"] == [] + response = api_client.put_json( + path=f"/api/v2/comments/{comment_id}/abuse_unflag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 200 + comment = Comment().get(comment_id) + assert comment is not None + assert comment["abuse_flaggers"] == [] def test_comment_flag_api_invalid_data(api_client: APIClient) -> None: @@ -99,13 +97,10 @@ def test_comment_flag_api_invalid_data(api_client: APIClient) -> None: """ user_id = "1" Users().insert(user_id, username="user1", email="email1") - mock_users_class = Mock(return_value=Users()) - mock_contents_class = Mock(return_value=Contents()) - with patch("forum.models.Users", new=mock_users_class): - with patch("forum.models.Contents", new=mock_contents_class): - response = api_client.put_json( - path="/api/v2/comments/66ace22474ba69001e1440bd/abuse_flag", - data={"user_id": str(user_id)}, - ) - assert response.status_code == 400 - assert response.json() == {"error": "User / Comment doesn't exist"} + + response = api_client.put_json( + path="/api/v2/comments/66ace22474ba69001e1440bd/abuse_flag", + data={"user_id": str(user_id)}, + ) + assert response.status_code == 400 + assert response.json() == {"error": "User / Comment doesn't exist"}