-
Notifications
You must be signed in to change notification settings - Fork 0
feat: added soft delete functionality #2
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
forum/api/threads.py
Outdated
| def bulk_soft_delete_threads( | ||
| thread_ids: list[str], user_id: str, course_id: Optional[str] = None | ||
| ) -> dict[str, Any]: | ||
| """ | ||
| Bulk soft delete threads for the given thread_ids. | ||
| Parameters: | ||
| thread_ids: List of thread IDs to be soft deleted. | ||
| user_id: The ID of the user performing the soft delete. | ||
| course_id: The course ID for backend selection. | ||
| Response: | ||
| Dictionary with success count and any errors. | ||
| """ | ||
| backend = get_backend(course_id)() | ||
|
|
||
| # Validate all threads exist and are not already deleted | ||
| # Also collect author info for stats updates | ||
| valid_thread_ids = [] | ||
| thread_authors = {} # {thread_id: author_id} | ||
| errors = [] | ||
|
|
||
| for thread_id in thread_ids: | ||
| try: | ||
| thread = backend.validate_object("CommentThread", thread_id) | ||
| # Treat NULL/missing as False for legacy data | ||
| if thread.get("is_deleted", False): | ||
| errors.append(f"Thread {thread_id} is already deleted") | ||
| else: | ||
| valid_thread_ids.append(thread_id) | ||
| thread_authors[thread_id] = thread.get("author_id") | ||
| except ObjectDoesNotExist: | ||
| errors.append(f"Thread does not exist with Id: {thread_id}") | ||
|
|
||
| if not valid_thread_ids: | ||
| raise ForumV2RequestError("No valid threads to soft delete") | ||
|
|
||
| result = backend.bulk_soft_delete_threads(valid_thread_ids, user_id) | ||
|
|
||
| # Update stats for each author | ||
| for thread_id in valid_thread_ids: | ||
| author_id = thread_authors.get(thread_id) | ||
| if author_id and course_id: | ||
| try: | ||
| backend.update_stats_for_course( | ||
| author_id, course_id, threads=-1, deleted_count=1 | ||
| ) | ||
| except Exception as e: | ||
| log.warning(f"Failed to update stats for thread {thread_id}: {e}") | ||
|
|
||
| return { | ||
| "success_count": result, | ||
| "errors": errors, | ||
| "processed_threads": valid_thread_ids, | ||
| } | ||
|
|
||
|
|
||
| def bulk_restore_threads( | ||
| thread_ids: list[str], course_id: Optional[str] = None | ||
| ) -> dict[str, Any]: | ||
| """ | ||
| Bulk restore soft deleted threads for the given thread_ids. | ||
| Parameters: | ||
| thread_ids: List of thread IDs to be restored. | ||
| course_id: The course ID for backend selection. | ||
| Response: | ||
| Dictionary with success count and any errors. | ||
| """ | ||
| backend = get_backend(course_id)() | ||
|
|
||
| # Validate all threads exist and are soft deleted | ||
| # Also collect author info for stats updates | ||
| valid_thread_ids = [] | ||
| thread_authors = {} # {thread_id: author_id} | ||
| errors = [] | ||
|
|
||
| for thread_id in thread_ids: | ||
| try: | ||
| thread = backend.validate_object("CommentThread", thread_id) | ||
| # Treat NULL/missing as False for legacy data | ||
| if not thread.get("is_deleted", False): | ||
| errors.append(f"Thread {thread_id} is not deleted") | ||
| else: | ||
| valid_thread_ids.append(thread_id) | ||
| thread_authors[thread_id] = thread.get("author_id") | ||
| except ObjectDoesNotExist: | ||
| errors.append(f"Thread does not exist with Id: {thread_id}") | ||
|
|
||
| if not valid_thread_ids: | ||
| raise ForumV2RequestError("No valid threads to restore") | ||
|
|
||
| result = backend.bulk_restore_threads(valid_thread_ids) | ||
|
|
||
| # Update stats for each author | ||
| for thread_id in valid_thread_ids: | ||
| author_id = thread_authors.get(thread_id) | ||
| if author_id and course_id: | ||
| try: | ||
| backend.update_stats_for_course( | ||
| author_id, course_id, threads=1, deleted_count=-1 | ||
| ) | ||
| except Exception as e: | ||
| log.warning(f"Failed to update stats for thread {thread_id}: {e}") | ||
|
|
||
| return { | ||
| "success_count": result, | ||
| "errors": errors, | ||
| "processed_threads": valid_thread_ids, | ||
| } | ||
|
|
||
|
|
||
| def get_deleted_threads( | ||
| course_id: str, | ||
| user_id: Optional[str] = None, | ||
| resp_skip: int = 0, | ||
| resp_limit: Optional[int] = None, | ||
| sort_key: Optional[str] = None, | ||
| ) -> dict[str, Any]: | ||
| """ | ||
| Get soft deleted threads for a course. | ||
| Parameters: | ||
| course_id: The course ID. | ||
| user_id: Optional user ID to filter by author. | ||
| resp_skip: Number of threads to skip for pagination. | ||
| resp_limit: Maximum number of threads to return. | ||
| sort_key: Sort key for ordering results. | ||
| Response: | ||
| Dictionary with deleted threads and pagination info. | ||
| """ | ||
| backend = get_backend(course_id)() | ||
|
|
||
| filters = {"course_id": course_id} | ||
| if user_id: | ||
| filters["author_id"] = user_id | ||
|
|
||
| threads_cursor = backend.get_deleted_list( | ||
| resp_skip=resp_skip, | ||
| resp_limit=resp_limit, | ||
| sort=sort_key, | ||
| **filters | ||
| ) | ||
|
|
||
| threads = [] | ||
| for thread in threads_cursor: | ||
| try: | ||
| serialized_thread = prepare_thread_api_response(thread, backend) | ||
| threads.append(serialized_thread) | ||
| except ValidationError as error: | ||
| log.error(f"Validation error in get_deleted_threads for thread {thread.get('_id', 'unknown')}: {error}") | ||
| continue | ||
| except Exception as error: | ||
| log.error(f"Unexpected error in get_deleted_threads for thread {thread.get('_id', 'unknown')}: {error}") | ||
| continue | ||
|
|
||
| return { | ||
| "threads": threads, | ||
| "count": len(threads), | ||
| "skip": resp_skip, | ||
| "limit": resp_limit, | ||
| } | ||
|
|
||
|
|
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.
There seems to be some duplication, can you compare and remove not used portion ?
forum/api/users.py
Outdated
| # Debug logging | ||
| print(f"[FORUM DEBUG] get_user_active_threads called") | ||
| print(f"[FORUM DEBUG] show_deleted={show_deleted} (type: {type(show_deleted)})") | ||
| print(f"[FORUM DEBUG] user_id={user_id}, course_id={course_id}") | ||
| print(f"[FORUM DEBUG] flagged={flagged}, unread={unread}, unanswered={unanswered}, unresponded={unresponded}") |
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.
Needs to be removed.
forum/api/users.py
Outdated
|
|
||
| params: dict[str, Any] = { | ||
| # Import backend check here to avoid circular imports | ||
| from forum.backend import is_mysql_backend_enabled |
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.
Linters will fail here, so you can add comment here to check
forum/api/users.py
Outdated
| print(f"[FORUM DEBUG] Using {backend.__class__.__name__} backend") | ||
| print(f"[FORUM DEBUG] show_deleted={show_deleted} -> {deleted_param_name}={deleted_param_value}") | ||
| print(f"[FORUM DEBUG] Calling handle_threads_query with thread_ids={len(active_thread_ids)}") | ||
| print(f"[FORUM DEBUG] Key params: {deleted_param_name}={params.get(deleted_param_name)}") | ||
|
|
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.
Can be removed
| def save(self, *args: Any, **kwargs: Any) -> None: | ||
| """Set author_username on creation if not already set.""" | ||
| if not self.pk and not self.author_username: | ||
| # On creation, store the current username | ||
| if self.retired_username: | ||
| self.author_username = self.retired_username | ||
| elif self.author: | ||
| self.author_username = self.author.username | ||
| super().save(*args, **kwargs) |
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.
Can you check why this is removed ?
| else: | ||
| self.stdout.write(f'Successfully updated existing posts with soft delete fields') | ||
|
|
||
| client.close() No newline at end of file |
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.
Last line missing
| @@ -1,0 +1 @@ | |||
| # Management commands | |||
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.
Need to check the base as these files already exist.
| @@ -0,0 +1,90 @@ | |||
| """ | |||
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.
We may not need a management command as there is no need for it now.
forum/urls.py
Outdated
| "threads/<str:thread_id>/soft-delete", | ||
| SoftDeleteThreadAPIView.as_view(), | ||
| name="soft-delete-thread-api", | ||
| ), |
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.
Do we need restore API
chintanjoshi-apphelix-2u
left a comment
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.
Some changes
- Check for duplication
- Check for debug/print/ statements
- Check for non-required comments and non-required code (i.e. management command)
009fde4 to
2f9e19e
Compare
2f9e19e to
17ce656
Compare
Description
Implements soft delete functionality for discussion threads, responses, and comments using the
is_deletedflag instead of permanently deleting records.Changes Made
JIRA Ticket
Related Tickets