Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion forum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Openedx forum app.
"""

__version__ = "0.4.0"
__version__ = "0.3.9"
13 changes: 4 additions & 9 deletions forum/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
create_parent_comment,
delete_comment,
get_course_id_by_comment,
get_deleted_comments_for_course,
get_parent_comment,
get_user_comments,
restore_comment,
restore_user_deleted_comments,
update_comment,
)
from .flags import update_comment_flag, update_thread_flag
from .flags import (
update_comment_flag,
update_thread_flag,
)
from .pins import pin_thread, unpin_thread
from .search import search_threads
from .subscriptions import (
Expand All @@ -28,11 +28,8 @@
create_thread,
delete_thread,
get_course_id_by_thread,
get_deleted_threads_for_course,
get_thread,
get_user_threads,
restore_thread,
restore_user_deleted_threads,
update_thread,
)
from .users import (
Expand Down Expand Up @@ -76,8 +73,6 @@
"get_user_course_stats",
"get_user_subscriptions",
"get_user_threads",
"get_deleted_comments_for_course",
"get_deleted_threads_for_course",
"mark_thread_as_read",
"pin_thread",
"retire_user",
Expand Down
96 changes: 6 additions & 90 deletions forum/api/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,12 @@ def update_comment(
raise error


def delete_comment(
comment_id: str, course_id: Optional[str] = None, deleted_by: Optional[str] = None
) -> dict[str, Any]:
def delete_comment(comment_id: str, course_id: Optional[str] = None) -> dict[str, Any]:
"""
Delete a comment.

Parameters:
comment_id: The ID of the comment to be deleted.
course_id: The ID of the course (optional).
deleted_by: The ID of the user performing the delete (optional).
Body:
Empty.
Response:
Expand All @@ -248,33 +244,14 @@ def delete_comment(
backend,
exclude_fields=["endorsement", "sk"],
)
backend.delete_comment(comment_id)
author_id = comment["author_id"]
comment_course_id = comment["course_id"]

# soft_delete_comment returns (responses_deleted, replies_deleted)
responses_deleted, replies_deleted = backend.soft_delete_comment(
comment_id, deleted_by
)

# Update stats based on what was actually deleted
if responses_deleted > 0:
# A response (parent comment) was deleted
backend.update_stats_for_course(
author_id,
comment_course_id,
responses=-responses_deleted,
deleted_responses=responses_deleted,
replies=-replies_deleted,
deleted_replies=replies_deleted,
)
parent_comment_id = data["parent_id"]
if parent_comment_id:
backend.update_stats_for_course(author_id, comment_course_id, replies=-1)
else:
# Only a reply was deleted (no response)
backend.update_stats_for_course(
author_id,
comment_course_id,
replies=-replies_deleted,
deleted_replies=replies_deleted,
)
backend.update_stats_for_course(author_id, comment_course_id, responses=-1)
Comment on lines +247 to +254
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stats are being updated without checking if the comment is anonymous. Anonymous comments should not update user course statistics. The function should check if the comment is anonymous or anonymous_to_peers before calling update_stats_for_course, similar to how thread deletion handles this check on line 191 of forum/api/threads.py.

Copilot uses AI. Check for mistakes.
return data


Expand Down Expand Up @@ -411,64 +388,3 @@ def get_user_comments(
"num_pages": num_pages,
"page": page,
}


def get_deleted_comments_for_course(
course_id: str, page: int = 1, per_page: int = 20, author_id: Optional[str] = None
) -> dict[str, Any]:
"""
Get deleted comments for a specific course.

Args:
course_id (str): The course identifier
page (int): Page number for pagination (default: 1)
per_page (int): Number of comments per page (default: 20)
author_id (str, optional): Filter by author ID

Returns:
dict: Dictionary containing deleted comments and pagination info
"""
backend = get_backend(course_id)()
return backend.get_deleted_comments_for_course(course_id, page, per_page, author_id)


def restore_comment(
comment_id: str, course_id: Optional[str] = None, restored_by: Optional[str] = None
) -> bool:
"""
Restore a soft-deleted comment.

Args:
comment_id (str): The ID of the comment to restore
course_id (str, optional): The course ID for backend selection
restored_by (str, optional): The ID of the user performing the restoration

Returns:
bool: True if comment was restored, False if not found
"""
backend = get_backend(course_id)()
return backend.restore_comment(comment_id, restored_by=restored_by)


def restore_user_deleted_comments(
user_id: str,
course_ids: list[str],
course_id: Optional[str] = None,
restored_by: Optional[str] = None,
) -> int:
"""
Restore all deleted comments for a user across courses.

Args:
user_id (str): The ID of the user whose comments to restore
course_ids (list): List of course IDs to restore comments in
course_id (str, optional): Course ID for backend selection (uses first from list if not provided)
restored_by (str, optional): The ID of the user performing the restoration

Returns:
int: Number of comments restored
"""
backend = get_backend(course_id or course_ids[0])()
return backend.restore_user_deleted_comments(
user_id, course_ids, restored_by=restored_by
)
2 changes: 0 additions & 2 deletions forum/api/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ def search_threads(
page: int = FORUM_DEFAULT_PAGE,
per_page: int = FORUM_DEFAULT_PER_PAGE,
is_moderator: bool = False,
is_deleted: bool = False,
) -> dict[str, Any]:
"""
Search for threads based on the provided data.
Expand Down Expand Up @@ -108,7 +107,6 @@ def search_threads(
raw_query=False,
commentable_ids=commentable_ids,
is_moderator=is_moderator,
is_deleted=is_deleted,
)

if collections := data.get("collection"):
Expand Down
83 changes: 4 additions & 79 deletions forum/api/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,12 @@ def get_thread(
raise ForumV2RequestError("Failed to prepare thread API response") from error


def delete_thread(
thread_id: str, course_id: Optional[str] = None, deleted_by: Optional[str] = None
) -> dict[str, Any]:
def delete_thread(thread_id: str, course_id: Optional[str] = None) -> dict[str, Any]:
"""
Delete the thread for the given thread_id.

Parameters:
thread_id: The ID of the thread to be deleted.
course_id: The ID of the course (optional).
deleted_by: The ID of the user performing the delete (optional).
Response:
The details of the thread that is deleted.
"""
Expand All @@ -181,9 +177,7 @@ def delete_thread(
f"Thread does not exist with Id: {thread_id}"
) from exc

count_of_response_deleted, count_of_replies_deleted = (
backend.soft_delete_comments_of_a_thread(thread_id, deleted_by)
)
backend.delete_comments_of_a_thread(thread_id)
thread = backend.validate_object("CommentThread", thread_id)

try:
Expand All @@ -193,17 +187,10 @@ def delete_thread(
raise ForumV2RequestError("Failed to prepare thread API response") from error

backend.delete_subscriptions_of_a_thread(thread_id)
result = backend.soft_delete_thread(thread_id, deleted_by)
result = backend.delete_thread(thread_id)
if result and not (thread["anonymous"] or thread["anonymous_to_peers"]):
backend.update_stats_for_course(
thread["author_id"],
thread["course_id"],
threads=-1,
responses=-count_of_response_deleted,
replies=-count_of_replies_deleted,
deleted_threads=1,
deleted_responses=count_of_response_deleted,
deleted_replies=count_of_replies_deleted,
thread["author_id"], thread["course_id"], threads=-1
)

return serialized_data
Expand Down Expand Up @@ -406,7 +393,6 @@ def get_user_threads(
"user_id": user_id,
"group_id": group_id,
"group_ids": group_ids,
"is_deleted": kwargs.get("is_deleted", False),
"context": kwargs.get("context"),
}
params = {k: v for k, v in params.items() if v is not None}
Expand Down Expand Up @@ -434,64 +420,3 @@ def get_course_id_by_thread(thread_id: str) -> str | None:
or MySQLBackend.get_course_id_by_thread_id(thread_id)
or None
)


def get_deleted_threads_for_course(
course_id: str, page: int = 1, per_page: int = 20, author_id: Optional[str] = None
) -> dict[str, Any]:
"""
Get deleted threads for a specific course.

Args:
course_id (str): The course identifier
page (int): Page number for pagination (default: 1)
per_page (int): Number of threads per page (default: 20)
author_id (str, optional): Filter by author ID

Returns:
dict: Dictionary containing deleted threads and pagination info
"""
backend = get_backend(course_id)()
return backend.get_deleted_threads_for_course(course_id, page, per_page, author_id)


def restore_thread(
thread_id: str, course_id: Optional[str] = None, restored_by: Optional[str] = None
) -> bool:
"""
Restore a soft-deleted thread.

Args:
thread_id (str): The ID of the thread to restore
course_id (str, optional): The course ID for backend selection
restored_by (str, optional): The ID of the user performing the restoration

Returns:
bool: True if thread was restored, False if not found
"""
backend = get_backend(course_id)()
return backend.restore_thread(thread_id, restored_by=restored_by)


def restore_user_deleted_threads(
user_id: str,
course_ids: list[str],
course_id: Optional[str] = None,
restored_by: Optional[str] = None,
) -> int:
"""
Restore all deleted threads for a user across courses.

Args:
user_id (str): The ID of the user whose threads to restore
course_ids (list): List of course IDs to restore threads in
course_id (str, optional): Course ID for backend selection (uses first from list if not provided)
restored_by (str, optional): The ID of the user performing the restoration

Returns:
int: Number of threads restored
"""
backend = get_backend(course_id or course_ids[0])()
return backend.restore_user_deleted_threads(
user_id, course_ids, restored_by=restored_by
)
2 changes: 0 additions & 2 deletions forum/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ def get_user_active_threads(
per_page: Optional[int] = FORUM_DEFAULT_PER_PAGE,
group_id: Optional[str] = None,
is_moderator: Optional[bool] = False,
show_deleted: Optional[bool] = False,
) -> dict[str, Any]:
"""Get user active threads."""
backend = get_backend(course_id)()
Expand Down Expand Up @@ -252,7 +251,6 @@ def get_user_active_threads(
"context": "course",
"raw_query": raw_query,
"is_moderator": is_moderator,
"is_deleted": show_deleted,
}
data = backend.handle_threads_query(**params)

Expand Down
24 changes: 0 additions & 24 deletions forum/backends/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,27 +476,3 @@ def get_user_contents_by_username(username: str) -> list[dict[str, Any]]:
Retrieve all threads and comments authored by a specific user.
"""
raise NotImplementedError

@staticmethod
def get_deleted_threads_for_course(
course_id: str,
page: int = 1,
per_page: int = 20,
author_id: Optional[str] = None,
) -> dict[str, Any]:
"""
Get deleted threads for a specific course.
"""
raise NotImplementedError

@staticmethod
def get_deleted_comments_for_course(
course_id: str,
page: int = 1,
per_page: int = 20,
author_id: Optional[str] = None,
) -> dict[str, Any]:
"""
Get deleted comments for a specific course.
"""
raise NotImplementedError
Loading